home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / lib / xulrunner-1.9.1.5 / components / nsExtensionManager.js < prev    next >
Text File  |  2009-11-09  |  341KB  |  8,815 lines

  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /*
  3. //@line 44 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  4. */
  5.  
  6. //
  7. // TODO:
  8. // - better logging
  9. //
  10.  
  11. const Cc = Components.classes;
  12. const Ci = Components.interfaces;
  13. const Cr = Components.results;
  14.  
  15. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  16.  
  17. const PREF_EM_CHECK_COMPATIBILITY     = "extensions.checkCompatibility";
  18. const PREF_EM_CHECK_UPDATE_SECURITY   = "extensions.checkUpdateSecurity";
  19. const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
  20. const PREF_EM_ENABLED_ITEMS           = "extensions.enabledItems";
  21. const PREF_UPDATE_COUNT               = "extensions.update.count";
  22. const PREF_UPDATE_DEFAULT_URL         = "extensions.update.url";
  23. const PREF_EM_NEW_ADDONS_LIST         = "extensions.newAddons";
  24. const PREF_EM_IGNOREMTIMECHANGES      = "extensions.ignoreMTimeChanges";
  25. const PREF_EM_DISABLEDOBSOLETE        = "extensions.disabledObsolete";
  26. const PREF_EM_EXTENSION_FORMAT        = "extensions.%UUID%.";
  27. const PREF_EM_ITEM_UPDATE_ENABLED     = "extensions.%UUID%.update.enabled";
  28. const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
  29. const PREF_EM_ITEM_UPDATE_URL         = "extensions.%UUID%.update.url";
  30. const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
  31. const PREF_DSS_SWITCHPENDING          = "extensions.dss.switchPending";
  32. const PREF_DSS_SKIN_TO_SELECT         = "extensions.lastSelectedSkin";
  33. const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
  34. const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
  35. const PREF_EM_UPDATE_INTERVAL         = "extensions.update.interval";
  36. const PREF_UPDATE_NOTIFYUSER          = "extensions.update.notifyUser";
  37. const PREF_MATCH_OS_LOCALE            = "intl.locale.matchOS";
  38. const PREF_SELECTED_LOCALE            = "general.useragent.locale";
  39.  
  40. const DIR_EXTENSIONS                  = "extensions";
  41. const DIR_CHROME                      = "chrome";
  42. const DIR_STAGE                       = "staged-xpis";
  43. const FILE_EXTENSIONS                 = "extensions.rdf";
  44. const FILE_EXTENSION_MANIFEST         = "extensions.ini";
  45. const FILE_EXTENSIONS_STARTUP_CACHE   = "extensions.cache";
  46. const FILE_EXTENSIONS_LOG             = "extensions.log";
  47. const FILE_AUTOREG                    = ".autoreg";
  48. const FILE_INSTALL_MANIFEST           = "install.rdf";
  49. const FILE_CONTENTS_MANIFEST          = "contents.rdf";
  50. const FILE_CHROME_MANIFEST            = "chrome.manifest";
  51.  
  52. const UNKNOWN_XPCOM_ABI               = "unknownABI";
  53.  
  54. const FILE_DEFAULT_THEME_JAR          = "classic.jar";
  55. const TOOLKIT_ID                      = "toolkit@mozilla.org"
  56.  
  57. const KEY_PROFILEDIR                  = "ProfD";
  58. const KEY_PROFILEDS                   = "ProfDS";
  59. const KEY_APPDIR                      = "XCurProcD";
  60. const KEY_GREDIR                      = "GreD";
  61. const KEY_TEMPDIR                     = "TmpD";
  62.  
  63. const EM_ACTION_REQUESTED_TOPIC       = "em-action-requested";
  64. const EM_ITEM_INSTALLED               = "item-installed";
  65. const EM_ITEM_UPGRADED                = "item-upgraded";
  66. const EM_ITEM_UNINSTALLED             = "item-uninstalled";
  67. const EM_ITEM_ENABLED                 = "item-enabled";
  68. const EM_ITEM_DISABLED                = "item-disabled";
  69. const EM_ITEM_CANCEL                  = "item-cancel-action";
  70.  
  71. const OP_NONE                         = "";
  72. const OP_NEEDS_INSTALL                = "needs-install";
  73. const OP_NEEDS_UPGRADE                = "needs-upgrade";
  74. const OP_NEEDS_UNINSTALL              = "needs-uninstall";
  75. const OP_NEEDS_ENABLE                 = "needs-enable";
  76. const OP_NEEDS_DISABLE                = "needs-disable";
  77.  
  78. const KEY_APP_PROFILE                 = "app-profile";
  79. const KEY_APP_GLOBAL                  = "app-global";
  80. const KEY_GRE_GLOBAL                  = "gre-global";
  81. const KEY_APP_SYSTEM_LOCAL            = "app-system-local";
  82. const KEY_APP_SYSTEM_SHARE            = "app-system-share";
  83. const KEY_APP_SYSTEM_USER             = "app-system-user";
  84.  
  85. const CATEGORY_INSTALL_LOCATIONS      = "extension-install-locations";
  86. const CATEGORY_UPDATE_PARAMS          = "extension-update-params";
  87.  
  88. const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
  89. const PREFIX_NS_CHROME                = "http://www.mozilla.org/rdf/chrome#";
  90. const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
  91. const PREFIX_EXTENSION                = "urn:mozilla:extension:";
  92. const PREFIX_THEME                    = "urn:mozilla:theme:";
  93. const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
  94. const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
  95. const RDFURI_DEFAULT_THEME            = "urn:mozilla:item:{972ce4c6-7e08-4474-a285-3208198ce6fd}";
  96. const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
  97.  
  98. const URI_GENERIC_ICON_XPINSTALL      = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
  99. const URI_GENERIC_ICON_THEME          = "chrome://mozapps/skin/extensions/themeGeneric.png";
  100. const URI_XPINSTALL_CONFIRM_DIALOG    = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
  101. const URI_EXTENSIONS_PROPERTIES       = "chrome://mozapps/locale/extensions/extensions.properties";
  102. const URI_BRAND_PROPERTIES            = "chrome://branding/locale/brand.properties";
  103. const URI_DOWNLOADS_PROPERTIES        = "chrome://mozapps/locale/downloads/downloads.properties";
  104. const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
  105. const URI_EXTENSION_LIST_DIALOG       = "chrome://mozapps/content/extensions/list.xul";
  106.  
  107. const INSTALLERROR_SUCCESS               = 0;
  108. const INSTALLERROR_INVALID_VERSION       = -1;
  109. const INSTALLERROR_INVALID_GUID          = -2;
  110. const INSTALLERROR_INCOMPATIBLE_VERSION  = -3;
  111. const INSTALLERROR_PHONING_HOME          = -4;
  112. const INSTALLERROR_INCOMPATIBLE_PLATFORM = -5;
  113. const INSTALLERROR_BLOCKLISTED           = -6;
  114. const INSTALLERROR_INSECURE_UPDATE       = -7;
  115. const INSTALLERROR_INVALID_MANIFEST      = -8;
  116. const INSTALLERROR_RESTRICTED            = -9;
  117. const INSTALLERROR_SOFTBLOCKED           = -10;
  118.  
  119. const MODE_RDONLY   = 0x01;
  120. const MODE_WRONLY   = 0x02;
  121. const MODE_CREATE   = 0x08;
  122. const MODE_APPEND   = 0x10;
  123. const MODE_TRUNCATE = 0x20;
  124.  
  125. const PERMS_FILE      = 0644;
  126. const PERMS_DIRECTORY = 0755;
  127.  
  128. const REQ_VERSION = 2;
  129.  
  130. var gApp  = null;
  131. var gPref = null;
  132. var gRDF  = null;
  133. var gOS   = null;
  134. var gEmSingleton          = null;
  135. var gBlocklist            = null;
  136. var gXPCOMABI             = null;
  137. var gOSTarget             = null;
  138. var gConsole              = null;
  139. var gInstallManifestRoot  = null;
  140. var gVersionChecker       = null;
  141. var gDirService           = null;
  142. var gLoggingEnabled       = null;
  143. var gCheckCompatibility   = true;
  144. var gCheckUpdateSecurity  = true;
  145. var gLocale               = "en-US";
  146. var gFirstRun             = false;
  147. var gAllowFlush           = true;
  148. var gDSNeedsFlush         = false;
  149. var gManifestNeedsFlush   = false;
  150.  
  151. /**
  152.  * Valid GUIDs fit this pattern.
  153.  */
  154. var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
  155.  
  156. // shared code for suppressing bad cert dialogs
  157. //@line 41 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/shared/src/badCertHandler.js"
  158.  
  159. /**
  160.  * Only allow built-in certs for HTTPS connections.  See bug 340198.
  161.  */
  162. function checkCert(channel) {
  163.   if (!channel.originalURI.schemeIs("https"))  // bypass
  164.     return;
  165.  
  166.   const Ci = Components.interfaces;  
  167.   var cert =
  168.       channel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider).
  169.       SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
  170.  
  171.   var issuer = cert.issuer;
  172.   while (issuer && !cert.equals(issuer)) {
  173.     cert = issuer;
  174.     issuer = cert.issuer;
  175.   }
  176.  
  177.   var errorstring = "cert issuer is not built-in";
  178.   if (!issuer)
  179.     throw errorstring;
  180.  
  181.   issuer = issuer.QueryInterface(Ci.nsIX509Cert3);
  182.   var tokenNames = issuer.getAllTokenNames({});
  183.  
  184.   if (!tokenNames.some(isBuiltinToken))
  185.     throw errorstring;
  186. }
  187.  
  188. function isBuiltinToken(tokenName) {
  189.   return tokenName == "Builtin Object Token";
  190. }
  191.  
  192. /**
  193.  * This class implements nsIBadCertListener.  Its job is to prevent "bad cert"
  194.  * security dialogs from being shown to the user.  It is better to simply fail
  195.  * if the certificate is bad. See bug 304286.
  196.  */
  197. function BadCertHandler() {
  198. }
  199. BadCertHandler.prototype = {
  200.  
  201.   // nsIChannelEventSink
  202.   onChannelRedirect: function(oldChannel, newChannel, flags) {
  203.     // make sure the certificate of the old channel checks out before we follow
  204.     // a redirect from it.  See bug 340198.
  205.     checkCert(oldChannel);
  206.   },
  207.  
  208.   // Suppress any certificate errors
  209.   notifyCertProblem: function(socketInfo, status, targetSite) {
  210.     return true;
  211.   },
  212.  
  213.   // Suppress any ssl errors
  214.   notifySSLError: function(socketInfo, error, targetSite) {
  215.     return true;
  216.   },
  217.  
  218.   // nsIInterfaceRequestor
  219.   getInterface: function(iid) {
  220.     return this.QueryInterface(iid);
  221.   },
  222.  
  223.   // nsISupports
  224.   QueryInterface: function(iid) {
  225.     if (!iid.equals(Components.interfaces.nsIChannelEventSink) &&
  226.         !iid.equals(Components.interfaces.nsIBadCertListener2) &&
  227.         !iid.equals(Components.interfaces.nsISSLErrorListener) &&
  228.         !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
  229.         !iid.equals(Components.interfaces.nsISupports))
  230.       throw Components.results.NS_ERROR_NO_INTERFACE;
  231.     return this;
  232.   }
  233. };
  234. //@line 198 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  235.  
  236. /**
  237.  * Creates a Version Checker object.
  238.  * @returns A handle to the global Version Checker service.
  239.  */
  240. function getVersionChecker() {
  241.   if (!gVersionChecker) {
  242.     gVersionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"].
  243.                       getService(Ci.nsIVersionComparator);
  244.   }
  245.   return gVersionChecker;
  246. }
  247.  
  248. var BundleManager = {
  249.   /**
  250.   * Creates and returns a String Bundle at the specified URI
  251.   * @param   bundleURI
  252.   *          The URI of the bundle to load
  253.   * @returns A nsIStringBundle which was retrieved.
  254.   */
  255.   getBundle: function BundleManager_getBundle(bundleURI) {
  256.     var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  257.               getService(Ci.nsIStringBundleService);
  258.     return sbs.createBundle(bundleURI);
  259.   },
  260.  
  261.   _appName: "",
  262.  
  263.   /**
  264.    * The Application's display name.
  265.    */
  266.   get appName() {
  267.     if (!this._appName) {
  268.       var brandBundle = this.getBundle(URI_BRAND_PROPERTIES)
  269.       this._appName = brandBundle.GetStringFromName("brandShortName");
  270.     }
  271.     return this._appName;
  272.   }
  273. };
  274.  
  275. ///////////////////////////////////////////////////////////////////////////////
  276. //
  277. // Utility Functions
  278. //
  279. function EM_NS(property) {
  280.   return PREFIX_NS_EM + property;
  281. }
  282.  
  283. function CHROME_NS(property) {
  284.   return PREFIX_NS_CHROME + property;
  285. }
  286.  
  287. function EM_R(property) {
  288.   return gRDF.GetResource(EM_NS(property));
  289. }
  290.  
  291. function EM_L(literal) {
  292.   return gRDF.GetLiteral(literal);
  293. }
  294.  
  295. function EM_I(integer) {
  296.   return gRDF.GetIntLiteral(integer);
  297. }
  298.  
  299. function EM_D(integer) {
  300.   return gRDF.GetDateLiteral(integer);
  301. }
  302.  
  303. /**
  304.  * Gets a preference value, handling the case where there is no default.
  305.  * @param   func
  306.  *          The name of the preference function to call, on nsIPrefBranch
  307.  * @param   preference
  308.  *          The name of the preference
  309.  * @param   defaultValue
  310.  *          The default value to return in the event the preference has
  311.  *          no setting
  312.  * @returns The value of the preference, or undefined if there was no
  313.  *          user or default value.
  314.  */
  315. function getPref(func, preference, defaultValue) {
  316.   try {
  317.     return gPref[func](preference);
  318.   }
  319.   catch (e) {
  320.   }
  321.   return defaultValue;
  322. }
  323.  
  324. /**
  325.  * Initializes a RDF Container at a URI in a datasource.
  326.  * @param   datasource
  327.  *          The datasource the container is in
  328.  * @param   root
  329.  *          The RDF Resource which is the root of the container.
  330.  * @returns The nsIRDFContainer, initialized at the root.
  331.  */
  332. function getContainer(datasource, root) {
  333.   var ctr = Cc["@mozilla.org/rdf/container;1"].
  334.             createInstance(Ci.nsIRDFContainer);
  335.   ctr.Init(datasource, root);
  336.   return ctr;
  337. }
  338.  
  339. /**
  340.  * Gets a RDF Resource for item with the given ID
  341.  * @param   id
  342.  *          The GUID of the item to construct a RDF resource to the
  343.  *          active item for
  344.  * @returns The RDF Resource to the Active item.
  345.  */
  346. function getResourceForID(id) {
  347.   return gRDF.GetResource(PREFIX_ITEM_URI + id);
  348. }
  349.  
  350. /**
  351.  * Construct a nsIUpdateItem with the supplied metadata
  352.  * ...
  353.  */
  354. function makeItem(id, version, locationKey, minVersion, maxVersion, name,
  355.                   updateURL, updateHash, iconURL, updateRDF, updateKey, type, 
  356.                   targetAppID) {
  357.   var item = new UpdateItem();
  358.   item.init(id, version, locationKey, minVersion, maxVersion, name,
  359.             updateURL, updateHash, iconURL, updateRDF, updateKey, type,
  360.             targetAppID);
  361.   return item;
  362. }
  363.  
  364. /**
  365.  * Gets the specified directory at the specified hierarchy under a
  366.  * Directory Service key.
  367.  * @param   key
  368.  *          The Directory Service Key to start from
  369.  * @param   pathArray
  370.  *          An array of path components to locate beneath the directory
  371.  *          specified by |key|
  372.  * @return  nsIFile object for the location specified. If the directory
  373.  *          requested does not exist, it is created, along with any
  374.  *          parent directories that need to be created.
  375.  */
  376. function getDir(key, pathArray) {
  377.   return getDirInternal(key, pathArray, true);
  378. }
  379.  
  380. /**
  381.  * Gets the specified directory at the specified hierarchy under a
  382.  * Directory Service key.
  383.  * @param   key
  384.  *          The Directory Service Key to start from
  385.  * @param   pathArray
  386.  *          An array of path components to locate beneath the directory
  387.  *          specified by |key|
  388.  * @return  nsIFile object for the location specified. If the directory
  389.  *          requested does not exist, it is NOT created.
  390.  */
  391. function getDirNoCreate(key, pathArray) {
  392.   return getDirInternal(key, pathArray, false);
  393. }
  394.  
  395. /**
  396.  * Gets the specified directory at the specified hierarchy under a
  397.  * Directory Service key.
  398.  * @param   key
  399.  *          The Directory Service Key to start from
  400.  * @param   pathArray
  401.  *          An array of path components to locate beneath the directory
  402.  *          specified by |key|
  403.  * @param   shouldCreate
  404.  *          true if the directory hierarchy specified in |pathArray|
  405.  *          should be created if it does not exist,
  406.  *          false otherwise.
  407.  * @return  nsIFile object for the location specified.
  408.  */
  409. function getDirInternal(key, pathArray, shouldCreate) {
  410.   if (!gDirService) {
  411.     gDirService = Cc["@mozilla.org/file/directory_service;1"].
  412.                   getService(Ci.nsIProperties);
  413.   }
  414.  
  415.   var dir = gDirService.get(key, Ci.nsILocalFile);
  416.   for (var i = 0; i < pathArray.length; ++i) {
  417.     dir.append(pathArray[i]);
  418.     if (shouldCreate && !dir.exists())
  419.       dir.create(Ci.nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  420.   }
  421.   dir.followLinks = false;
  422.   return dir;
  423. }
  424.  
  425. /**
  426.  * Gets the file at the specified hierarchy under a Directory Service key.
  427.  * @param   key
  428.  *          The Directory Service Key to start from
  429.  * @param   pathArray
  430.  *          An array of path components to locate beneath the directory
  431.  *          specified by |key|. The last item in this array must be the
  432.  *          leaf name of a file.
  433.  * @return  nsIFile object for the file specified. The file is NOT created
  434.  *          if it does not exist, however all required directories along
  435.  *          the way are.
  436.  */
  437. function getFile(key, pathArray) {
  438.   var file = getDir(key, pathArray.slice(0, -1));
  439.   file.append(pathArray[pathArray.length - 1]);
  440.   return file;
  441. }
  442.  
  443. /**
  444.  * Gets the descriptor of a directory as a relative path to common base
  445.  * directories (profile, user home, app install dir, etc).
  446.  *
  447.  * @param   itemLocation
  448.  *          The nsILocalFile representing the item's directory.
  449.  * @param   installLocation the nsIInstallLocation for this item
  450.  */
  451. function getDescriptorFromFile(itemLocation, installLocation) {
  452.   var baseDir = installLocation.location;
  453.  
  454.   if (baseDir && baseDir.contains(itemLocation, true)) {
  455.     return "rel%" + itemLocation.getRelativeDescriptor(baseDir);
  456.   }
  457.  
  458.   return "abs%" + itemLocation.persistentDescriptor;
  459. }
  460.  
  461. function getAbsoluteDescriptor(itemLocation) {
  462.   return itemLocation.persistentDescriptor;
  463. }
  464.  
  465. /**
  466.  * Initializes a Local File object based on a descriptor
  467.  * provided by "getDescriptorFromFile".
  468.  *
  469.  * @param   descriptor
  470.  *          The descriptor that locates the directory
  471.  * @param   installLocation
  472.  *          The nsIInstallLocation object for this item.
  473.  * @returns The nsILocalFile object representing the location of the item
  474.  */
  475. function getFileFromDescriptor(descriptor, installLocation) {
  476.   var location = Cc["@mozilla.org/file/local;1"].
  477.                  createInstance(Ci.nsILocalFile);
  478.  
  479.   var m = descriptor.match(/^(abs|rel)\%(.*)$/);
  480.   if (!m)
  481.     throw Cr.NS_ERROR_INVALID_ARG;
  482.  
  483.   if (m[1] == "rel") {
  484.     location.setRelativeDescriptor(installLocation.location, m[2]);
  485.   }
  486.   else {
  487.     location.persistentDescriptor = m[2];
  488.   }
  489.  
  490.   return location;
  491. }
  492.  
  493. /**
  494.  * Determines if a file is an item package - either a XPI or a JAR file.
  495.  * @param   file
  496.  *          The file to check
  497.  * @returns true if the file is an item package, false otherwise.
  498.  */
  499. function fileIsItemPackage(file) {
  500.   var fileURL = getURIFromFile(file);
  501.   if (fileURL instanceof Ci.nsIURL)
  502.     var extension = fileURL.fileExtension.toLowerCase();
  503.   return extension == "xpi" || extension == "jar";
  504. }
  505.  
  506. /**
  507.  * Opens a safe file output stream for writing.
  508.  * @param   file
  509.  *          The file to write to.
  510.  * @param   modeFlags
  511.  *          (optional) File open flags. Can be undefined.
  512.  * @returns nsIFileOutputStream to write to.
  513.  */
  514. function openSafeFileOutputStream(file, modeFlags) {
  515.   var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  516.             createInstance(Ci.nsIFileOutputStream);
  517.   if (modeFlags === undefined)
  518.     modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
  519.   if (!file.exists())
  520.     file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  521.   fos.init(file, modeFlags, PERMS_FILE, 0);
  522.   return fos;
  523. }
  524.  
  525. /**
  526.  * Closes a safe file output stream.
  527.  * @param   stream
  528.  *          The stream to close.
  529.  */
  530. function closeSafeFileOutputStream(stream) {
  531.   if (stream instanceof Ci.nsISafeOutputStream)
  532.     stream.finish();
  533.   else
  534.     stream.close();
  535. }
  536.  
  537. /**
  538.  * Deletes a directory and its children. First it tries nsIFile::Remove(true).
  539.  * If that fails it will fall back to recursing, setting the appropriate
  540.  * permissions, and deleting the current entry. This is needed for when we have
  541.  * rights to delete a directory but there are entries that have a read-only
  542.  * attribute (e.g. a copy restore from a read-only CD, etc.)
  543.  * @param   dir
  544.  *          A nsIFile for the directory to be deleted
  545.  */
  546. function removeDirRecursive(dir) {
  547.   try {
  548.     dir.remove(true);
  549.     return;
  550.   }
  551.   catch (e) {
  552.   }
  553.  
  554.   var dirEntries = dir.directoryEntries;
  555.   while (dirEntries.hasMoreElements()) {
  556.     var entry = dirEntries.getNext().QueryInterface(Ci.nsIFile);
  557.  
  558.     if (entry.isDirectory()) {
  559.       removeDirRecursive(entry);
  560.     }
  561.     else {
  562.       entry.permissions = PERMS_FILE;
  563.       entry.remove(false);
  564.     }
  565.   }
  566.   dir.permissions = PERMS_DIRECTORY;
  567.   dir.remove(true);
  568. }
  569.  
  570. /**
  571.  * Logs a string to the error console.
  572.  * @param   string
  573.  *          The string to write to the error console.
  574.  */
  575. function LOG(string) {
  576.   if (gLoggingEnabled) {
  577.     dump("*** " + string + "\n");
  578.     if (gConsole)
  579.       gConsole.logStringMessage(string);
  580.   }
  581. }
  582.  
  583. /**
  584.  * Logs a string to the error console and to a permanent log file. 
  585.  * @param   string
  586.  *          The string to write out.
  587.  */  
  588. function ERROR(string) {
  589.   LOG(string);
  590.   try {
  591.     var tstamp = new Date();
  592.     var logfile = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_LOG]);
  593.     var stream = Cc["@mozilla.org/network/file-output-stream;1"].
  594.                  createInstance(Ci.nsIFileOutputStream);
  595.     stream.init(logfile, 0x02 | 0x08 | 0x10, 0666, 0); // write, create, append
  596.     var writer = Cc["@mozilla.org/intl/converter-output-stream;1"].
  597.                  createInstance(Ci.nsIConverterOutputStream);
  598.     writer.init(stream, "UTF-8", 0, 0x0000);
  599.     string = tstamp.toLocaleFormat("%Y-%m-%d %H:%M:%S - ") + string;
  600.     writer.writeString(string + "\n");
  601.     writer.close();
  602.   }
  603.   catch (e) { }
  604. }
  605.  
  606. /**
  607.  * Randomize the specified file name. Used to force RDF to bypass the cache
  608.  * when loading certain types of files.
  609.  * @param   fileName
  610.  *          A file name to randomize, e.g. install.rdf
  611.  * @returns A randomized file name, e.g. install-xyz.rdf
  612.  */
  613. function getRandomFileName(fileName) {
  614.   var extensionDelimiter = fileName.lastIndexOf(".");
  615.   var prefix = fileName.substr(0, extensionDelimiter);
  616.   var suffix = fileName.substr(extensionDelimiter);
  617.  
  618.   var characters = "abcdefghijklmnopqrstuvwxyz0123456789";
  619.   var nameString = prefix + "-";
  620.   for (var i = 0; i < 3; ++i) {
  621.     var index = Math.round((Math.random()) * characters.length);
  622.     nameString += characters.charAt(index);
  623.   }
  624.   return nameString + "." + suffix;
  625. }
  626.  
  627. /**
  628.  * Get the RDF URI prefix of a nsIUpdateItem type. This function should be used
  629.  * ONLY to support Firefox 1.0 Update RDF files! Item URIs in the datasource
  630.  * are NOT prefixed.
  631.  * @param   type
  632.  *          The nsIUpdateItem type to find a RDF URI prefix for
  633.  * @returns The RDF URI prefix.
  634.  */
  635. function getItemPrefix(type) {
  636.   if (type & Ci.nsIUpdateItem.TYPE_EXTENSION)
  637.     return PREFIX_EXTENSION;
  638.   else if (type & Ci.nsIUpdateItem.TYPE_THEME)
  639.     return PREFIX_THEME;
  640.   return PREFIX_ITEM_URI;
  641. }
  642.  
  643. /**
  644.  * Trims a prefix from a string.
  645.  * @param   string
  646.  *          The source string
  647.  * @param   prefix
  648.  *          The prefix to remove.
  649.  * @returns The suffix (string - prefix)
  650.  */
  651. function stripPrefix(string, prefix) {
  652.   return string.substr(prefix.length);
  653. }
  654.  
  655. /**
  656.  * Gets a File URL spec for a nsIFile
  657.  * @param   file
  658.  *          The file to get a file URL spec to
  659.  * @returns The file URL spec to the file
  660.  */
  661. function getURLSpecFromFile(file) {
  662.   var ioServ = Cc["@mozilla.org/network/io-service;1"].
  663.                getService(Ci.nsIIOService);
  664.   var fph = ioServ.getProtocolHandler("file")
  665.                   .QueryInterface(Ci.nsIFileProtocolHandler);
  666.   return fph.getURLSpecFromFile(file);
  667. }
  668.  
  669. /**
  670.  * Constructs a URI to a spec.
  671.  * @param   spec
  672.  *          The spec to construct a URI to
  673.  * @returns The nsIURI constructed.
  674.  */
  675. function newURI(spec) {
  676.   var ioServ = Cc["@mozilla.org/network/io-service;1"].
  677.                getService(Ci.nsIIOService);
  678.   return ioServ.newURI(spec, null, null);
  679. }
  680.  
  681. /**
  682.  * Constructs a File URI to a nsIFile
  683.  * @param   file
  684.  *          The file to construct a File URI to
  685.  * @returns The file URI to the file
  686.  */
  687. function getURIFromFile(file) {
  688.   var ioServ = Cc["@mozilla.org/network/io-service;1"].
  689.                getService(Ci.nsIIOService);
  690.   return ioServ.newFileURI(file);
  691. }
  692.  
  693. /**
  694.  * @returns Whether or not we are currently running in safe mode.
  695.  */
  696. function inSafeMode() {
  697.   return gApp.inSafeMode;
  698. }
  699.  
  700. /**
  701.  * Extract the string value from a RDF Literal or Resource
  702.  * @param   literalOrResource
  703.  *          RDF String Literal or Resource
  704.  * @returns String value of the literal or resource, or undefined if the object
  705.  *          supplied is not a RDF string literal or resource.
  706.  */
  707. function stringData(literalOrResource) {
  708.   if (literalOrResource instanceof Ci.nsIRDFLiteral)
  709.     return literalOrResource.Value;
  710.   if (literalOrResource instanceof Ci.nsIRDFResource)
  711.     return literalOrResource.Value;
  712.   return undefined;
  713. }
  714.  
  715. /**
  716.  * Extract the integer value of a RDF Literal
  717.  * @param   literal
  718.  *          nsIRDFInt literal
  719.  * @return  integer value of the literal
  720.  */
  721. function intData(literal) {
  722.   if (literal instanceof Ci.nsIRDFInt)
  723.     return literal.Value;
  724.   return undefined;
  725. }
  726.  
  727. /**
  728.  * Gets a property from an install manifest.
  729.  * @param   installManifest
  730.  *          An Install Manifest datasource to read from
  731.  * @param   property
  732.  *          The name of a proprety to read (sans EM_NS)
  733.  * @returns The literal value of the property, or undefined if the property has
  734.  *          no value.
  735.  */
  736. function getManifestProperty(installManifest, property) {
  737.   var target = installManifest.GetTarget(gInstallManifestRoot,
  738.                                          gRDF.GetResource(EM_NS(property)), true);
  739.   var val = stringData(target);
  740.   return val === undefined ? intData(target) : val;
  741. }
  742.  
  743. /**
  744.  * Given an Install Manifest Datasource, retrieves the type of item the manifest
  745.  * describes.
  746.  * @param   installManifest
  747.  *          The Install Manifest Datasource.
  748.  * @return  The nsIUpdateItem type of the item described by the manifest
  749.  *          returns TYPE_EXTENSION if attempts to determine the type fail.
  750.  */
  751. function getAddonTypeFromInstallManifest(installManifest) {
  752.   var target = installManifest.GetTarget(gInstallManifestRoot,
  753.                                          gRDF.GetResource(EM_NS("type")), true);
  754.   if (target) {
  755.     var type = stringData(target);
  756.     return type === undefined ? intData(target) : parseInt(type);
  757.   }
  758.  
  759.   // Firefox 1.0 and earlier did not support addon-type annotation on the
  760.   // Install Manifest, so we fall back to a theme-only property to
  761.   // differentiate.
  762.   if (getManifestProperty(installManifest, "internalName") !== undefined)
  763.     return Ci.nsIUpdateItem.TYPE_THEME;
  764.  
  765.   // If no type is provided, default to "Extension"
  766.   return Ci.nsIUpdateItem.TYPE_EXTENSION;
  767. }
  768.  
  769. /**
  770.  * Shows a message about an incompatible Extension/Theme.
  771.  * @param   installData
  772.  *          An Install Data object from |getInstallData|
  773.  */
  774. function showIncompatibleError(installData) {
  775.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  776.   var params = [extensionStrings.GetStringFromName("type-" + installData.type)];
  777.   var title = extensionStrings.formatStringFromName("incompatibleTitle",
  778.                                                     params, params.length);
  779.   params = [installData.name, installData.version, BundleManager.appName,
  780.             gApp.version];
  781.   var message = extensionStrings.formatStringFromName("incompatibleMessage",
  782.                                                       params, params.length);
  783.   var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  784.            getService(Ci.nsIPromptService);
  785.   ps.alert(null, title, message);
  786. }
  787.  
  788. /**
  789.  * Shows a message.
  790.  * @param   titleKey
  791.  *          String key of the title string in the Extensions localization file.
  792.  * @param   messageKey
  793.  *          String key of the message string in the Extensions localization file.
  794.  * @param   messageParams
  795.  *          Array of strings to be substituted into |messageKey|. Can be null.
  796.  */
  797. function showMessage(titleKey, titleParams, messageKey, messageParams) {
  798.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  799.   if (titleParams && titleParams.length > 0) {
  800.     var title = extensionStrings.formatStringFromName(titleKey, titleParams,
  801.                                                       titleParams.length);
  802.   }
  803.   else
  804.     title = extensionStrings.GetStringFromName(titleKey);
  805.  
  806.   if (messageParams && messageParams.length > 0) {
  807.     var message = extensionStrings.formatStringFromName(messageKey, messageParams,
  808.                                                         messageParams.length);
  809.   }
  810.   else
  811.     message = extensionStrings.GetStringFromName(messageKey);
  812.   var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  813.            getService(Ci.nsIPromptService);
  814.   ps.alert(null, title, message);
  815. }
  816.  
  817. /**
  818.  * Shows a dialog for a blocklisted item. For soft blocked items this will
  819.  * return true if the item should still be installed
  820.  * @param   item
  821.  *          The nsIUpdateItem that is blocklisted
  822.  * @param   softblocked
  823.  *          True if this item is only soft blocked and may still be installed.
  824.  */
  825. function showBlocklistMessage(item, softblocked) {
  826.   var params = Cc["@mozilla.org/embedcomp/dialogparam;1"].
  827.                createInstance(Ci.nsIDialogParamBlock);
  828.   params.SetInt(0, softblocked ? 1 : 0);
  829.   params.SetInt(1, 0);
  830.   params.SetNumberStrings(1);
  831.   params.SetString(0, item.name + " " + item.version);
  832.  
  833.   var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  834.            getService(Ci.nsIWindowMediator);
  835.   var win = wm.getMostRecentWindow("Extension:Manager");
  836.   var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  837.            getService(Ci.nsIWindowWatcher);
  838.   ww.openWindow(win, URI_EXTENSION_LIST_DIALOG, "",
  839.                 "chrome,centerscreen,modal,dialog,titlebar", params);
  840.  
  841.   return params.GetInt(1) == 0 ? false : true;
  842. }
  843.  
  844. /**
  845.  * Gets a zip reader for the file specified.
  846.  * @param   zipFile
  847.  *          A ZIP archive to open with a nsIZipReader.
  848.  * @return  A nsIZipReader for the file specified.
  849.  */
  850. function getZipReaderForFile(zipFile) {
  851.   try {
  852.     var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
  853.                     createInstance(Ci.nsIZipReader);
  854.     zipReader.open(zipFile);
  855.   }
  856.   catch (e) {
  857.     zipReader.close();
  858.     throw e;
  859.   }
  860.   return zipReader;
  861. }
  862.  
  863. /**
  864.  * Extract a RDF file from a ZIP archive to a random location in the system
  865.  * temp directory.
  866.  * @param   zipFile
  867.  *          A ZIP archive to read from
  868.  * @param   fileName
  869.  *          The name of the file to read from the zip.
  870.  * @param   suppressErrors
  871.  *          Whether or not to report errors.
  872.  * @return  The file created in the temp directory.
  873.  */
  874. function extractRDFFileToTempDir(zipFile, fileName, suppressErrors) {
  875.   var file = getFile(KEY_TEMPDIR, [getRandomFileName(fileName)]);
  876.   try {
  877.     var zipReader = getZipReaderForFile(zipFile);
  878.     zipReader.extract(fileName, file);
  879.     zipReader.close();
  880.   }
  881.   catch (e) {
  882.     if (!suppressErrors) {
  883.       showMessage("missingFileTitle", [], "missingFileMessage",
  884.                   [BundleManager.appName, fileName]);
  885.       throw e;
  886.     }
  887.   }
  888.   return file;
  889. }
  890.  
  891. /**
  892.  * Gets an Install Manifest datasource from a file.
  893.  * @param   file
  894.  *          The nsIFile that contains the Install Manifest RDF
  895.  * @returns The Install Manifest datasource
  896.  */
  897. function getInstallManifest(file) {
  898.   var uri = getURIFromFile(file);
  899.   try {
  900.     var fis = Cc["@mozilla.org/network/file-input-stream;1"].
  901.               createInstance(Ci.nsIFileInputStream);
  902.     fis.init(file, -1, -1, false);
  903.     var bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
  904.               createInstance(Ci.nsIBufferedInputStream);
  905.     bis.init(fis, 4096);
  906.     
  907.     var rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
  908.                     createInstance(Ci.nsIRDFXMLParser)
  909.     var ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
  910.              createInstance(Ci.nsIRDFDataSource);
  911.     var listener = rdfParser.parseAsync(ds, uri);
  912.     var channel = Cc["@mozilla.org/network/input-stream-channel;1"].
  913.                   createInstance(Ci.nsIInputStreamChannel);
  914.     channel.setURI(uri);
  915.     channel.contentStream = bis;
  916.     channel.QueryInterface(Ci.nsIChannel);
  917.     channel.contentType = "text/xml";
  918.   
  919.     listener.onStartRequest(channel, null);
  920.     try {
  921.       var pos = 0;
  922.       var count = bis.available();
  923.       while (count > 0) {
  924.         listener.onDataAvailable(channel, null, bis, pos, count);
  925.         pos += count;
  926.         count = bis.available();
  927.       }
  928.       listener.onStopRequest(channel, null, Components.results.NS_OK);
  929.       bis.close();
  930.       fis.close();
  931.  
  932.       var arcs = ds.ArcLabelsOut(gInstallManifestRoot);
  933.       if (arcs.hasMoreElements())
  934.         return ds;
  935.     }
  936.     catch (e) {
  937.       listener.onStopRequest(channel, null, e.result);
  938.       bis.close();
  939.       fis.close();
  940.     }
  941.   }
  942.   catch (e) { }
  943.  
  944.   var url = uri.QueryInterface(Ci.nsIURL);
  945.   showMessage("malformedTitle", [], "malformedMessage",
  946.               [BundleManager.appName, url.fileName]);
  947.   return null;
  948. }
  949.  
  950. /**
  951.  * Selects the closest matching localized resource in the given RDF resource
  952.  * @param   aDataSource The datasource to look in
  953.  * @param   aResource   The root resource containing the localized sections
  954.  * @returns The nsIRDFResource of the best em:localized section or null
  955.  *          if no valid match was found
  956.  */
  957. function findClosestLocalizedResource(aDataSource, aResource) {
  958.   var localizedProp = EM_R("localized");
  959.   var localeProp = EM_R("locale");
  960.  
  961.   // Holds the best matching localized resource
  962.   var bestmatch = null;
  963.   // The number of locale parts it matched with
  964.   var bestmatchcount = 0;
  965.   // The number of locale parts in the match
  966.   var bestpartcount = 0;
  967.  
  968.   var locales = [gLocale.toLowerCase()];
  969.   /* If the current locale is English then it will find a match if there is
  970.      a valid match for en-US so no point searching that locale too. */
  971.   if (locales[0].substring(0, 3) != "en-")
  972.     locales.push("en-us");
  973.  
  974.   for each (var locale in locales) {
  975.     var lparts = locale.split("-");
  976.     var localizations = aDataSource.GetTargets(aResource, localizedProp, true);
  977.     while (localizations.hasMoreElements()) {
  978.       var localized = localizations.getNext().QueryInterface(Ci.nsIRDFNode);
  979.       var list = aDataSource.GetTargets(localized, localeProp, true);
  980.       while (list.hasMoreElements()) {
  981.         var found = stringData(list.getNext().QueryInterface(Ci.nsIRDFNode));
  982.         if (!found)
  983.           continue;
  984.  
  985.         found = found.toLowerCase();
  986.  
  987.         // Exact match is returned immediately
  988.         if (locale == found)
  989.           return localized;
  990.   
  991.         var fparts = found.split("-");
  992.         /* If we have found a possible match and this one isn't any longer
  993.            then we dont need to check further. */
  994.         if (bestmatch && fparts.length < bestmatchcount)
  995.           continue;
  996.   
  997.         // Count the number of parts that match
  998.         var maxmatchcount = Math.min(fparts.length, lparts.length);
  999.         var matchcount = 0;
  1000.         while (matchcount < maxmatchcount &&
  1001.                fparts[matchcount] == lparts[matchcount])
  1002.           matchcount++;
  1003.   
  1004.         /* If we matched more than the last best match or matched the same and
  1005.            this locale is less specific than the last best match. */
  1006.         if (matchcount > bestmatchcount ||
  1007.            (matchcount == bestmatchcount && fparts.length < bestpartcount)) {
  1008.           bestmatch = localized;
  1009.           bestmatchcount = matchcount;
  1010.           bestpartcount = fparts.length;
  1011.         }
  1012.       }
  1013.     }
  1014.     // If we found a valid match for this locale return it
  1015.     if (bestmatch)
  1016.       return bestmatch;
  1017.   }
  1018.   return null;
  1019. }
  1020.     
  1021. /**
  1022.  * An enumeration of items in a JS array.
  1023.  * @constructor
  1024.  */
  1025. function ArrayEnumerator(aItems) {
  1026.   if (aItems) {
  1027.     for (var i = 0; i < aItems.length; ++i) {
  1028.       if (!aItems[i])
  1029.         aItems.splice(i--, 1);
  1030.     }
  1031.     this._contents = aItems;
  1032.   } else {
  1033.     this._contents = [];
  1034.   }
  1035. }
  1036.  
  1037. ArrayEnumerator.prototype = {
  1038.   _index: 0,
  1039.  
  1040.   hasMoreElements: function ArrayEnumerator_hasMoreElements() {
  1041.     return this._index < this._contents.length;
  1042.   },
  1043.  
  1044.   getNext: function ArrayEnumerator_getNext() {
  1045.     return this._contents[this._index++];
  1046.   }
  1047. };
  1048.  
  1049. /**
  1050.  * An enumeration of files in a JS array.
  1051.  * @param   files
  1052.  *          The files to enumerate
  1053.  * @constructor
  1054.  */
  1055. function FileEnumerator(files) {
  1056.   if (files) {
  1057.     for (var i = 0; i < files.length; ++i) {
  1058.       if (!files[i])
  1059.         files.splice(i--, 1);
  1060.     }
  1061.     this._contents = files;
  1062.   } else {
  1063.     this._contents = [];
  1064.   }
  1065. }
  1066.  
  1067. FileEnumerator.prototype = {
  1068.   _index: 0,
  1069.  
  1070.   /**
  1071.    * Gets the next file in the sequence.
  1072.    */
  1073.   get nextFile() {
  1074.     if (this._index < this._contents.length)
  1075.       return this._contents[this._index++];
  1076.     return null;
  1077.   },
  1078.  
  1079.   /**
  1080.    * Stop enumerating. Nothing to do here.
  1081.    */
  1082.   close: function FileEnumerator_close() {
  1083.   }
  1084. };
  1085.  
  1086. /**
  1087.  * An object which identifies an Install Location for items, where the location
  1088.  * relationship is each item living in a directory named with its GUID under
  1089.  * the directory used when constructing this object.
  1090.  *
  1091.  * e.g. <location>\{GUID1}
  1092.  *      <location>\{GUID2}
  1093.  *      <location>\{GUID3}
  1094.  *      ...
  1095.  *
  1096.  * @param   name
  1097.  *          The string identifier of this Install Location.
  1098.  * @param   location
  1099.  *          The directory that contains the items.
  1100.  * @constructor
  1101.  */
  1102. function DirectoryInstallLocation(name, location, restricted, priority, independent) {
  1103.   this._name = name;
  1104.   if (location.exists()) {
  1105.     if (!location.isDirectory())
  1106.       throw new Error("location must be a directoy!");
  1107.   }
  1108.   else {
  1109.     try {
  1110.       location.create(Ci.nsILocalFile.DIRECTORY_TYPE, 0775);
  1111.     }
  1112.     catch (e) {
  1113.       LOG("DirectoryInstallLocation: failed to create location " +
  1114.           " directory = " + location.path + ", exception = " + e + "\n");
  1115.     }
  1116.   }
  1117.  
  1118.   this._location = location;
  1119.   this._locationToIDMap = {};
  1120.   this._restricted = restricted;
  1121.   this._priority = priority;
  1122.   this._independent = independent;
  1123. }
  1124. DirectoryInstallLocation.prototype = {
  1125.   _name           : "",
  1126.   _location       : null,
  1127.   _locationToIDMap: null,
  1128.   _restricted     : false,
  1129.   _priority       : 0,
  1130.   _independent    : false,
  1131.   _canAccess      : null,
  1132.  
  1133.   /**
  1134.    * See nsIExtensionManager.idl
  1135.    */
  1136.   get name() {
  1137.     return this._name;
  1138.   },
  1139.  
  1140.   /**
  1141.    * Reads a directory linked to in a file.
  1142.    * @param   file
  1143.    *          The file containing the directory path
  1144.    * @returns A nsILocalFile object representing the linked directory.
  1145.    */
  1146.   _readDirectoryFromFile: function DirInstallLocation__readDirectoryFromFile(file) {
  1147.     var fis = Cc["@mozilla.org/network/file-input-stream;1"].
  1148.               createInstance(Ci.nsIFileInputStream);
  1149.     fis.init(file, -1, -1, false);
  1150.     var line = { value: "" };
  1151.     if (fis instanceof Ci.nsILineInputStream)
  1152.       fis.readLine(line);
  1153.     fis.close();
  1154.     if (line.value) {
  1155.       var linkedDirectory = Cc["@mozilla.org/file/local;1"].
  1156.                             createInstance(Ci.nsILocalFile);
  1157.       try {
  1158.         linkedDirectory.initWithPath(line.value);
  1159.       }
  1160.       catch (e) {
  1161.         linkedDirectory.setRelativeDescriptor(file.parent, line.value);
  1162.       }
  1163.  
  1164.       return linkedDirectory;
  1165.     }
  1166.     return null;
  1167.   },
  1168.  
  1169.   /**
  1170.    * See nsIExtensionManager.idl
  1171.    */
  1172.   get itemLocations() {
  1173.     var locations = [];
  1174.     if (!this._location.exists())
  1175.       return new FileEnumerator(locations);
  1176.  
  1177.     try {
  1178.       var entries = this._location.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  1179.       while (true) {
  1180.         var entry = entries.nextFile;
  1181.         if (!entry)
  1182.           break;
  1183.         entry instanceof Ci.nsILocalFile;
  1184.         if (!entry.isDirectory() && gIDTest.test(entry.leafName)) {
  1185.           var linkedDirectory = this._readDirectoryFromFile(entry);
  1186.           if (linkedDirectory && linkedDirectory.exists() &&
  1187.               linkedDirectory.isDirectory()) {
  1188.             locations.push(linkedDirectory);
  1189.             this._locationToIDMap[linkedDirectory.persistentDescriptor] = entry.leafName;
  1190.           }
  1191.         }
  1192.         else
  1193.           locations.push(entry);
  1194.       }
  1195.       entries.close();
  1196.     }
  1197.     catch (e) {
  1198.     }
  1199.     return new FileEnumerator(locations);
  1200.   },
  1201.  
  1202.   /**
  1203.    * Retrieves the GUID for an item at the specified location.
  1204.    * @param   file
  1205.    *          The location where an item might live.
  1206.    * @returns The ID for an item that might live at the location specified.
  1207.    *
  1208.    * N.B. This function makes no promises about whether or not this path is
  1209.    *      actually maintained by this Install Location.
  1210.    */
  1211.   getIDForLocation: function DirInstallLocation_getIDForLocation(file) {
  1212.     var section = file.leafName;
  1213.     var filePD = file.persistentDescriptor;
  1214.     if (filePD in this._locationToIDMap)
  1215.       section = this._locationToIDMap[filePD];
  1216.  
  1217.     if (gIDTest.test(section))
  1218.       return RegExp.$1;
  1219.     return undefined;
  1220.   },
  1221.  
  1222.   /**
  1223.    * See nsIExtensionManager.idl
  1224.    */
  1225.   get location() {
  1226.     return this._location.clone();
  1227.   },
  1228.  
  1229.   /**
  1230.    * See nsIExtensionManager.idl
  1231.    */
  1232.   get restricted() {
  1233.     return this._restricted;
  1234.   },
  1235.  
  1236.   /**
  1237.    * See nsIExtensionManager.idl
  1238.    */
  1239.   get canAccess() {
  1240.     if (this._canAccess != null)
  1241.       return this._canAccess;
  1242.  
  1243.     if (!this.location.exists()) {
  1244.       this._canAccess = false;
  1245.       return false;
  1246.     }
  1247.  
  1248.     var testFile = this.location;
  1249.     testFile.append("Access Privileges Test");
  1250.     try {
  1251.       testFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1252.       testFile.remove(false);
  1253.       this._canAccess = true;
  1254.     }
  1255.     catch (e) {
  1256.       this._canAccess = false;
  1257.     }
  1258.     return this._canAccess;
  1259.   },
  1260.  
  1261.   /**
  1262.    * See nsIExtensionManager.idl
  1263.    */
  1264.   get priority() {
  1265.     return this._priority;
  1266.   },
  1267.  
  1268.   /**
  1269.    * See nsIExtensionManager.idl
  1270.    */
  1271.   getItemLocation: function DirInstallLocation_getItemLocation(id) {
  1272.     var itemLocation = this.location;
  1273.     itemLocation.append(id);
  1274.     if (itemLocation.exists() && !itemLocation.isDirectory())
  1275.       return this._readDirectoryFromFile(itemLocation);
  1276.     if (!itemLocation.exists() && this.canAccess)
  1277.       itemLocation.create(Ci.nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1278.     return itemLocation;
  1279.   },
  1280.  
  1281.   /**
  1282.    * See nsIExtensionManager.idl
  1283.    */
  1284.   itemIsManagedIndependently: function DirInstallLocation_itemIsManagedIndependently(id) {
  1285.     if (this._independent)
  1286.       return true;
  1287.     var itemLocation = this.location;
  1288.     itemLocation.append(id);
  1289.     return itemLocation.exists() && !itemLocation.isDirectory();
  1290.   },
  1291.  
  1292.   /**
  1293.    * See nsIExtensionManager.idl
  1294.    */
  1295.   getItemFile: function DirInstallLocation_getItemFile(id, filePath) {
  1296.     var itemLocation = this.getItemLocation(id).clone();
  1297.     var parts = filePath.split("/");
  1298.     for (var i = 0; i < parts.length; ++i)
  1299.       itemLocation.append(parts[i]);
  1300.     return itemLocation;
  1301.   },
  1302.  
  1303.   /**
  1304.    * Stages the specified file for later.
  1305.    * @param   file
  1306.    *          The file to stage
  1307.    * @param   id
  1308.    *          The GUID of the item the file represents
  1309.    */
  1310.   stageFile: function DirInstallLocation_stageFile(file, id) {
  1311.     var stagedFile = this.location;
  1312.     stagedFile.append(DIR_STAGE);
  1313.     stagedFile.append(id);
  1314.     stagedFile.append(file.leafName);
  1315.  
  1316.     // When an incompatible update is successful the file is already staged
  1317.     if (stagedFile.equals(file))
  1318.       return stagedFile;
  1319.  
  1320.     if (stagedFile.exists())
  1321.       stagedFile.remove(false);
  1322.  
  1323.     file.copyTo(stagedFile.parent, stagedFile.leafName);
  1324.  
  1325.     // If the file has incorrect permissions set, correct them now.
  1326.     if (!stagedFile.isWritable())
  1327.       stagedFile.permissions = PERMS_FILE;
  1328.  
  1329.     return stagedFile;
  1330.   },
  1331.  
  1332.   /**
  1333.    * Returns the most recently staged package (e.g. the last XPI or JAR in a
  1334.    * directory) for an item and removes items that do not qualify.
  1335.    * @param   id
  1336.    *          The ID of the staged package
  1337.    * @returns an nsIFile if the package exists otherwise null.
  1338.    */
  1339.   getStageFile: function DirInstallLocation_getStageFile(id) {
  1340.     var stageFile = null;
  1341.     var stageDir = this.location;
  1342.     stageDir.append(DIR_STAGE);
  1343.     stageDir.append(id);
  1344.     if (!stageDir.exists() || !stageDir.isDirectory())
  1345.       return null;
  1346.     try {
  1347.       var entries = stageDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  1348.       while (entries.hasMoreElements()) {
  1349.         var file = entries.nextFile;
  1350.         if (!(file instanceof Ci.nsILocalFile))
  1351.           continue;
  1352.         if (file.isDirectory())
  1353.           removeDirRecursive(file);
  1354.         else if (fileIsItemPackage(file)) {
  1355.           if (stageFile)
  1356.             stageFile.remove(false);
  1357.           stageFile = file;
  1358.         }
  1359.         else
  1360.           file.remove(false);
  1361.       }
  1362.     }
  1363.     catch (e) {
  1364.     }
  1365.     if (entries instanceof Ci.nsIDirectoryEnumerator)
  1366.       entries.close();
  1367.     return stageFile;
  1368.   },
  1369.  
  1370.   /**
  1371.    * Removes a file from the stage. This cleans up the stage if there is nothing
  1372.    * else left after the remove operation.
  1373.    * @param   file
  1374.    *          The file to remove.
  1375.    */
  1376.   removeFile: function DirInstallLocation_removeFile(file) {
  1377.     if (file.exists())
  1378.       file.remove(false);
  1379.     var parent = file.parent;
  1380.     var entries = parent.directoryEntries;
  1381.     try {
  1382.       // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
  1383.       // it has been removed will cause a crash on Mac OS X - bug 292823
  1384.       while (parent && !parent.equals(this.location) &&
  1385.             !entries.hasMoreElements()) {
  1386.         parent.remove(false);
  1387.         parent = parent.parent;
  1388.         entries = parent.directoryEntries;
  1389.       }
  1390.       if (entries instanceof Ci.nsIDirectoryEnumerator)
  1391.         entries.close();
  1392.     }
  1393.     catch (e) {
  1394.       ERROR("DirectoryInstallLocation::removeFile: failed to remove staged " +
  1395.             " directory = " + parent.path + ", exception = " + e + "\n");
  1396.     }
  1397.   },
  1398.  
  1399.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIInstallLocation])
  1400. };
  1401.  
  1402. //@line 1511 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  1403.  
  1404. /**
  1405.  * An object which handles the installation of an Extension.
  1406.  * @constructor
  1407.  */
  1408. function Installer(ds, id, installLocation, type) {
  1409.   this._ds = ds;
  1410.   this._id = id;
  1411.   this._type = type;
  1412.   this._installLocation = installLocation;
  1413. }
  1414. Installer.prototype = {
  1415.   // Item metadata
  1416.   _id: null,
  1417.   _ds: null,
  1418.   _installLocation: null,
  1419.   _metadataDS: null,
  1420.  
  1421.   /**
  1422.    * Gets the Install Manifest datasource we are installing from.
  1423.    */
  1424.   get metadataDS() {
  1425.     if (!this._metadataDS) {
  1426.       var metadataFile = this._installLocation
  1427.                              .getItemFile(this._id, FILE_INSTALL_MANIFEST);
  1428.       if (!metadataFile.exists())
  1429.         return null;
  1430.       this._metadataDS = getInstallManifest(metadataFile);
  1431.       if (!this._metadataDS) {
  1432.         LOG("Installer::install: metadata datasource for extension " +
  1433.             this._id + " at " + metadataFile.path + " could not be loaded. " +
  1434.             " Installation will not proceed.");
  1435.       }
  1436.     }
  1437.     return this._metadataDS;
  1438.   },
  1439.  
  1440.   /**
  1441.    * Installs the Extension
  1442.    * @param   file
  1443.    *          A XPI/JAR file to install from. If this is null or does not exist,
  1444.    *          the item is assumed to be an expanded directory, located at the GUID
  1445.    *          key in the supplied Install Location.
  1446.    */
  1447.   installFromFile: function Installer_installFromFile(file) {
  1448.     // Move files from the staging dir into the extension's final home.
  1449.     if (file && file.exists()) {
  1450.       this._installExtensionFiles(file);
  1451.     }
  1452.  
  1453.     if (!this.metadataDS)
  1454.       return;
  1455.  
  1456.     // Upgrade old-style contents.rdf Chrome Manifests if necessary.
  1457.     if (this._type == Ci.nsIUpdateItem.TYPE_THEME)
  1458.       this.upgradeThemeChrome();
  1459.     else
  1460.       this.upgradeExtensionChrome();
  1461.  
  1462.     // Add metadata for the extension to the global extension metadata set
  1463.     this._ds.addItemMetadata(this._id, this.metadataDS, this._installLocation);
  1464.   },
  1465.  
  1466.   /**
  1467.    * Safely extract the Extension's files into the target folder.
  1468.    * @param   file
  1469.    *          The XPI/JAR file to install from.
  1470.    */
  1471.   _installExtensionFiles: function Installer__installExtensionFiles(file) {
  1472.     /**
  1473.       * Callback for |safeInstallOperation| that performs file level installation
  1474.       * steps for an Extension.
  1475.       * @param   extensionID
  1476.       *          The GUID of the Extension being installed.
  1477.       * @param   installLocation
  1478.       *          The Install Location where the Extension is being installed.
  1479.       * @param   xpiFile
  1480.       *          The source XPI file that contains the Extension.
  1481.       */
  1482.     function extractExtensionFiles(extensionID, installLocation, xpiFile) {
  1483.       // Create a logger to log install operations for uninstall. This must be
  1484.       // created in the |safeInstallOperation| callback, since it creates a file
  1485.       // in the target directory. If we do this outside of the callback, we may
  1486.       // be clobbering a file we should not be.
  1487.       var zipReader = getZipReaderForFile(xpiFile);
  1488.  
  1489.       // create directories first
  1490.       var entries = zipReader.findEntries("*/");
  1491.       while (entries.hasMore()) {
  1492.         var entryName = entries.getNext();
  1493.         var target = installLocation.getItemFile(extensionID, entryName);
  1494.         if (!target.exists()) {
  1495.           try {
  1496.             target.create(Ci.nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1497.           }
  1498.           catch (e) {
  1499.             ERROR("extractExtensionsFiles: failed to create target directory for extraction " +
  1500.                   " file = " + target.path + ", exception = " + e + "\n");
  1501.           }
  1502.         }
  1503.       }
  1504.  
  1505.       entries = zipReader.findEntries(null);
  1506.       while (entries.hasMore()) {
  1507.         var entryName = entries.getNext();
  1508.         target = installLocation.getItemFile(extensionID, entryName);
  1509.         if (target.exists())
  1510.           continue;
  1511.  
  1512.         try {
  1513.           target.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1514.         }
  1515.         catch (e) {
  1516.           ERROR("extractExtensionsFiles: failed to create target file for extraction " +
  1517.                 " file = " + target.path + ", exception = " + e + "\n");
  1518.         }
  1519.         zipReader.extract(entryName, target);
  1520.       }
  1521.       zipReader.close();
  1522.     }
  1523.  
  1524.     /**
  1525.       * Callback for |safeInstallOperation| that performs file level installation
  1526.       * steps for a Theme.
  1527.       * @param   id
  1528.       *          The GUID of the Theme being installed.
  1529.       * @param   installLocation
  1530.       *          The Install Location where the Theme is being installed.
  1531.       * @param   jarFile
  1532.       *          The source JAR file that contains the Theme.
  1533.       */
  1534.     function extractThemeFiles(id, installLocation, jarFile) {
  1535.       var themeDirectory = installLocation.getItemLocation(id);
  1536.       var zipReader = getZipReaderForFile(jarFile);
  1537.  
  1538.       // The only critical file is the install.rdf and we would not have
  1539.       // gotten this far without one.
  1540.       var rootFiles = [FILE_INSTALL_MANIFEST, FILE_CHROME_MANIFEST,
  1541.                        "preview.png", "icon.png"];
  1542.       for (var i = 0; i < rootFiles.length; ++i) {
  1543.         try {
  1544.           var target = installLocation.getItemFile(id, rootFiles[i]);
  1545.           zipReader.extract(rootFiles[i], target);
  1546.         }
  1547.         catch (e) {
  1548.         }
  1549.       }
  1550.  
  1551.       var manifestFile = installLocation.getItemFile(id, FILE_CHROME_MANIFEST);
  1552.       // new theme structure requires a chrome.manifest file
  1553.       if (manifestFile.exists()) {
  1554.         var entries = zipReader.findEntries(DIR_CHROME + "/*");
  1555.         while (entries.hasMore()) {
  1556.           var entryName = entries.getNext();
  1557.           if (entryName.charAt(entryName.length - 1) == "/")
  1558.             continue;
  1559.           target = installLocation.getItemFile(id, entryName);
  1560.           try {
  1561.             target.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1562.           }
  1563.           catch (e) {
  1564.             ERROR("extractThemeFiles: failed to create target file for extraction " +
  1565.                   " file = " + target.path + ", exception = " + e + "\n");
  1566.           }
  1567.           zipReader.extract(entryName, target);
  1568.         }
  1569.         zipReader.close();
  1570.       }
  1571.       else { // old theme structure requires only an install.rdf
  1572.         try {
  1573.           var contentsManifestFile = installLocation.getItemFile(id, FILE_CONTENTS_MANIFEST);
  1574.           contentsManifestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1575.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  1576.         }
  1577.         catch (e) {
  1578.           zipReader.close();
  1579.           ERROR("extractThemeFiles: failed to extract contents.rdf: " + target.path);
  1580.           throw e; // let the safe-op clean up
  1581.         }
  1582.         zipReader.close();
  1583.         var chromeDir = installLocation.getItemFile(id, DIR_CHROME);
  1584.         try {
  1585.           jarFile.copyTo(chromeDir, jarFile.leafName);
  1586.         }
  1587.         catch (e) {
  1588.           ERROR("extractThemeFiles: failed to copy theme JAR file to: " + chromeDir.path);
  1589.           throw e; // let the safe-op clean up
  1590.         }
  1591.  
  1592.         if (!installer.metadataDS && installer._type == Ci.nsIUpdateItem.TYPE_THEME) {
  1593.           var themeName = extensionStrings.GetStringFromName("incompatibleThemeName");
  1594.           if (contentsManifestFile && contentsManifestFile.exists()) {
  1595.             var contentsManifest = gRDF.GetDataSourceBlocking(getURLSpecFromFile(contentsManifestFile));
  1596.             try {
  1597.               var ctr = getContainer(contentsManifest,
  1598.                                      gRDF.GetResource("urn:mozilla:skin:root"));
  1599.               var elts = ctr.GetElements();
  1600.               var nameArc = gRDF.GetResource(CHROME_NS("displayName"));
  1601.               while (elts.hasMoreElements()) {
  1602.                 var elt = elts.getNext().QueryInterface(Ci.nsIRDFResource);
  1603.                 themeName = stringData(contentsManifest.GetTarget(elt, nameArc, true));
  1604.                 if (themeName)
  1605.                   break;
  1606.               }
  1607.             }
  1608.             catch (e) {
  1609.               themeName = extensionStrings.GetStringFromName("incompatibleThemeName");
  1610.             }
  1611.           }
  1612.           showIncompatibleError({ name: themeName, version: "",
  1613.                                   type: Ci.nsIUpdateItem.TYPE_THEME });
  1614.           LOG("Theme JAR file: " + jarFile.leafName + " contains an Old-Style " +
  1615.               "Theme that is not compatible with this version of the software.");
  1616.           throw new Error("Old Theme"); // let the safe-op clean up
  1617.         }
  1618.       }
  1619.     }
  1620.  
  1621.     var installer = this;
  1622.     var callback = extractExtensionFiles;
  1623.     if (this._type == Ci.nsIUpdateItem.TYPE_THEME)
  1624.       callback = extractThemeFiles;
  1625.     safeInstallOperation(this._id, this._installLocation,
  1626.                           { callback: callback, data: file });
  1627.   },
  1628.  
  1629.   /**
  1630.    * Upgrade contents.rdf Chrome Manifests used by this Theme to the new
  1631.    * chrome.manifest format if necessary.
  1632.    */
  1633.   upgradeThemeChrome: function Installer_upgradeThemeChrome() {
  1634.     // Use the Chrome Registry API to install the theme there
  1635.     var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
  1636.              getService(Ci.nsIToolkitChromeRegistry);
  1637.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1638.     if (manifestFile.exists() ||
  1639.         this._id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  1640.       return;
  1641.  
  1642.     try {
  1643.       // creates a chrome manifest for themes
  1644.       var manifestURI = getURIFromFile(manifestFile);
  1645.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1646.       // We're relying on the fact that there is only one JAR file
  1647.       // in the "chrome" directory. This is a hack, but it works.
  1648.       var entries = chromeDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  1649.       var jarFile = entries.nextFile;
  1650.       if (jarFile) {
  1651.         var jarFileURI = getURIFromFile(jarFile);
  1652.         var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  1653.         var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1654.         var contentsFileURI = getURIFromFile(contentsFile.parent);
  1655.  
  1656.         cr.processContentsManifest(contentsFileURI, manifestURI, contentsURI, false, true);
  1657.       }
  1658.       entries.close();
  1659.       contentsFile.remove(false);
  1660.     }
  1661.     catch (e) {
  1662.       // Failed to register chrome, for any number of reasons - non-existent
  1663.       // contents.rdf file at the location specified, malformed contents.rdf,
  1664.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the
  1665.       // extension is uninstalled properly during the subsequent uninstall
  1666.       // pass in |ExtensionManager::_finishOperations|
  1667.       ERROR("upgradeThemeChrome: failed for theme " + this._id + " - why " +
  1668.             "not convert to the new chrome.manifest format while you're at it? " +
  1669.             "Failure exception: " + e);
  1670.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1671.                   [BundleManager.appName]);
  1672.  
  1673.       var stageFile = this._installLocation.getStageFile(this._id);
  1674.       if (stageFile)
  1675.         this._installLocation.removeFile(stageFile);
  1676.  
  1677.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1678.       StartupCache.write();
  1679.     }
  1680.   },
  1681.  
  1682.   /**
  1683.    * Upgrade contents.rdf Chrome Manifests used by this Extension to the new
  1684.    * chrome.manifest format if necessary.
  1685.    */
  1686.   upgradeExtensionChrome: function Installer_upgradeExtensionChrome() {
  1687.     // If the extension is aware of the new flat chrome manifests and has
  1688.     // included one, just use it instead of generating one from the
  1689.     // install.rdf/contents.rdf data.
  1690.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1691.     if (manifestFile.exists())
  1692.       return;
  1693.  
  1694.     try {
  1695.       // Enumerate the metadata datasource files collection and register chrome
  1696.       // for each file, calling _registerChrome for each.
  1697.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1698.  
  1699.       if (!manifestFile.parent.exists())
  1700.         return;
  1701.  
  1702.       // Even if an extension doesn't have any chrome, we generate an empty
  1703.       // manifest file so that we don't try to upgrade from the "old-style"
  1704.       // chrome manifests at every startup.
  1705.       manifestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1706.  
  1707.       var manifestURI = getURIFromFile(manifestFile);
  1708.       var files = this.metadataDS.GetTargets(gInstallManifestRoot, EM_R("file"), true);
  1709.       while (files.hasMoreElements()) {
  1710.         var file = files.getNext().QueryInterface(Ci.nsIRDFResource);
  1711.         var chromeFile = chromeDir.clone();
  1712.         var fileName = file.Value.substr("urn:mozilla:extension:file:".length, file.Value.length);
  1713.         chromeFile.append(fileName);
  1714.  
  1715.         var fileURLSpec = getURLSpecFromFile(chromeFile);
  1716.         if (!chromeFile.isDirectory()) {
  1717.           var zipReader = getZipReaderForFile(chromeFile);
  1718.           fileURLSpec = "jar:" + fileURLSpec + "!/";
  1719.           var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1720.           contentsFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1721.         }
  1722.  
  1723.         var providers = [EM_R("package"), EM_R("skin"), EM_R("locale")];
  1724.         for (var i = 0; i < providers.length; ++i) {
  1725.           var items = this.metadataDS.GetTargets(file, providers[i], true);
  1726.           while (items.hasMoreElements()) {
  1727.             var item = items.getNext().QueryInterface(Ci.nsIRDFLiteral);
  1728.             var fileURI = newURI(fileURLSpec + item.Value);
  1729.             // Extract the contents.rdf files instead of opening them inside of
  1730.             // the jar. This prevents the jar from being cached by the zip
  1731.             // reader which will keep the jar in use and prevent deletion.
  1732.             if (zipReader) {
  1733.               zipReader.extract(item.Value + FILE_CONTENTS_MANIFEST, contentsFile);
  1734.               var contentsFileURI = getURIFromFile(contentsFile.parent);
  1735.             }
  1736.             else
  1737.               contentsFileURI = fileURI;
  1738.  
  1739.             var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
  1740.                      getService(Ci.nsIToolkitChromeRegistry);
  1741.             cr.processContentsManifest(contentsFileURI, manifestURI, fileURI, true, false);
  1742.           }
  1743.         }
  1744.         if (zipReader) {
  1745.           zipReader.close();
  1746.           zipReader = null;
  1747.           contentsFile.remove(false);
  1748.         }
  1749.       }
  1750.     }
  1751.     catch (e) {
  1752.       // Failed to register chrome, for any number of reasons - non-existent
  1753.       // contents.rdf file at the location specified, malformed contents.rdf,
  1754.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the
  1755.       // extension is uninstalled properly during the subsequent uninstall
  1756.       // pass in |ExtensionManager::_finishOperations|
  1757.       ERROR("upgradeExtensionChrome: failed for extension " + this._id + " - why " +
  1758.             "not convert to the new chrome.manifest format while you're at it? " +
  1759.             "Failure exception: " + e);
  1760.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1761.                   [BundleManager.appName]);
  1762.  
  1763.       var stageFile = this._installLocation.getStageFile(this._id);
  1764.       if (stageFile)
  1765.         this._installLocation.removeFile(stageFile);
  1766.  
  1767.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1768.       StartupCache.write();
  1769.     }
  1770.   }
  1771. };
  1772.  
  1773. /**
  1774.  * Safely attempt to perform a caller-defined install operation for a given
  1775.  * item ID. Using aggressive success-safety checks, this function will attempt
  1776.  * to move an existing location for an item aside and then allow installation
  1777.  * into the appropriate folder. If any operation fails the installation will
  1778.  * abort and roll back from the moved-aside old version.
  1779.  * @param   itemID
  1780.  *          The GUID of the item to perform the operation on.
  1781.  * @param   installLocation
  1782.  *          The Install Location where the item is installed.
  1783.  * @param   installCallback
  1784.  *          A caller supplied JS object with the following properties:
  1785.  *          "data"      A data parameter to be passed to the callback.
  1786.  *          "callback"  A function to perform the install operation. This
  1787.  *                      function is passed three parameters:
  1788.  *                      1. The GUID of the item being operated on.
  1789.  *                      2. The Install Location where the item is installed.
  1790.  *                      3. The "data" parameter on the installCallback object.
  1791.  */
  1792. function safeInstallOperation(itemID, installLocation, installCallback) {
  1793.   var movedFiles = [];
  1794.  
  1795.   /**
  1796.    * Reverts a deep move by moving backed up files back to their original
  1797.    * location.
  1798.    */
  1799.   function rollbackMove()
  1800.   {
  1801.     for (var i = 0; i < movedFiles.length; ++i) {
  1802.       var oldFile = movedFiles[i].oldFile;
  1803.       var newFile = movedFiles[i].newFile;
  1804.       try {
  1805.         newFile.moveTo(oldFile.parent, newFile.leafName);
  1806.       }
  1807.       catch (e) {
  1808.         ERROR("safeInstallOperation: failed to roll back files after an install " +
  1809.               "operation failed. Failed to roll back: " + newFile.path + " to: " +
  1810.               oldFile.path + " ... aborting installation.");
  1811.         throw e;
  1812.       }
  1813.     }
  1814.   }
  1815.  
  1816.   /**
  1817.    * Moves a file to a new folder.
  1818.    * @param   file
  1819.    *          The file to move
  1820.    * @param   destination
  1821.    *          The target folder
  1822.    */
  1823.   function moveFile(file, destination) {
  1824.     try {
  1825.       var oldFile = file.clone();
  1826.       file.moveTo(destination, file.leafName);
  1827.       movedFiles.push({ oldFile: oldFile, newFile: file });
  1828.     }
  1829.     catch (e) {
  1830.       ERROR("safeInstallOperation: failed to back up file: " + file.path + " to: " +
  1831.             destination.path + " ... rolling back file moves and aborting " +
  1832.             "installation.");
  1833.       rollbackMove();
  1834.       throw e;
  1835.     }
  1836.   }
  1837.  
  1838.   /**
  1839.    * Moves a directory to a new location. If any part of the move fails,
  1840.    * files already moved will be rolled back.
  1841.    * @param   sourceDir
  1842.    *          The directory to move
  1843.    * @param   targetDir
  1844.    *          The destination directory
  1845.    * @param   currentDir
  1846.    *          The current directory (a subdirectory of |sourceDir| or
  1847.    *          |sourceDir| itself) we are moving files from.
  1848.    */
  1849.   function moveDirectory(sourceDir, targetDir, currentDir) {
  1850.     var entries = currentDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  1851.     while (true) {
  1852.       var entry = entries.nextFile;
  1853.       if (!entry)
  1854.         break;
  1855.       if (entry.isDirectory())
  1856.         moveDirectory(sourceDir, targetDir, entry);
  1857.       else if (entry instanceof Ci.nsILocalFile) {
  1858.         var rd = entry.getRelativeDescriptor(sourceDir);
  1859.         var destination = targetDir.clone().QueryInterface(Ci.nsILocalFile);
  1860.         destination.setRelativeDescriptor(targetDir, rd);
  1861.         moveFile(entry, destination.parent);
  1862.       }
  1863.     }
  1864.     entries.close();
  1865.   }
  1866.  
  1867.   /**
  1868.    * Removes the temporary backup directory where we stored files.
  1869.    * @param   directory
  1870.    *          The backup directory to remove
  1871.    */
  1872.   function cleanUpTrash(directory) {
  1873.     try {
  1874.       // Us-generated. Safe.
  1875.       if (directory && directory.exists())
  1876.         removeDirRecursive(directory);
  1877.     }
  1878.     catch (e) {
  1879.       ERROR("safeInstallOperation: failed to clean up the temporary backup of the " +
  1880.             "older version: " + itemLocationTrash.path);
  1881.       // This is a non-fatal error. Annoying, but non-fatal.
  1882.     }
  1883.   }
  1884.  
  1885.   if (!installLocation.itemIsManagedIndependently(itemID)) {
  1886.     var itemLocation = installLocation.getItemLocation(itemID);
  1887.     if (itemLocation.exists()) {
  1888.       var trashDirName = itemID + "-trash";
  1889.       var itemLocationTrash = itemLocation.parent.clone();
  1890.       itemLocationTrash.append(trashDirName);
  1891.       if (itemLocationTrash.exists()) {
  1892.         // We can remove recursively here since this is a folder we created, not
  1893.         // one the user specified. If this fails, it'll throw, and the caller
  1894.         // should stop installation.
  1895.         try {
  1896.           removeDirRecursive(itemLocationTrash);
  1897.         }
  1898.         catch (e) {
  1899.           ERROR("safeFileOperation: failed to remove existing trash directory " +
  1900.                 itemLocationTrash.path + " ... aborting installation.");
  1901.           throw e;
  1902.         }
  1903.       }
  1904.  
  1905.       // Move the directory that contains the existing version of the item aside,
  1906.       // into {GUID}-trash. This will throw if there's a failure and the install
  1907.       // will abort.
  1908.       moveDirectory(itemLocation, itemLocationTrash, itemLocation);
  1909.  
  1910.       // Clean up the original location, if necessary. Again, this is a path we
  1911.       // generated, so it is safe to recursively delete.
  1912.       try {
  1913.         removeDirRecursive(itemLocation);
  1914.       }
  1915.       catch (e) {
  1916.         ERROR("safeInstallOperation: failed to clean up item location after its contents " +
  1917.               "were properly backed up. Failed to clean up: " + itemLocation.path +
  1918.               " ... rolling back file moves and aborting installation.");
  1919.         rollbackMove();
  1920.         cleanUpTrash(itemLocationTrash);
  1921.         throw e;
  1922.       }
  1923.     }
  1924.   }
  1925.   else if (installLocation.name == KEY_APP_PROFILE ||
  1926.            installLocation.name == KEY_GRE_GLOBAL ||
  1927.            installLocation.name == KEY_APP_GLOBAL ||
  1928.            installLocation.name == KEY_APP_SYSTEM_USER) {
  1929.     // Check for a pointer file and move it aside if it exists
  1930.     var pointerFile = installLocation.location.clone();
  1931.     pointerFile.append(itemID);
  1932.     if (pointerFile.exists() && !pointerFile.isDirectory()) {
  1933.       var trashFileName = itemID + "-trash";
  1934.       var itemLocationTrash = installLocation.location.clone();
  1935.       itemLocationTrash.append(trashFileName);
  1936.       if (itemLocationTrash.exists()) {
  1937.         // We can remove recursively here since this is a folder we created, not
  1938.         // one the user specified. If this fails, it'll throw, and the caller
  1939.         // should stop installation.
  1940.         try {
  1941.           removeDirRecursive(itemLocationTrash);
  1942.         }
  1943.         catch (e) {
  1944.           ERROR("safeFileOperation: failed to remove existing trash directory " +
  1945.                 itemLocationTrash.path + " ... aborting installation.");
  1946.           throw e;
  1947.         }
  1948.       }
  1949.       itemLocationTrash.create(Ci.nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1950.       // Move the pointer file to the trash.
  1951.       moveFile(pointerFile, itemLocationTrash);
  1952.     }
  1953.   }
  1954.  
  1955.   // Now tell the client to do their stuff.
  1956.   try {
  1957.     installCallback.callback(itemID, installLocation, installCallback.data);
  1958.   }
  1959.   catch (e) {
  1960.     // This means the install operation failed. Remove everything and roll back.
  1961.     ERROR("safeInstallOperation: install operation (caller-supplied callback) failed, " +
  1962.           "rolling back file moves and aborting installation.");
  1963.     try {
  1964.       // Us-generated. Safe.
  1965.       removeDirRecursive(itemLocation);
  1966.     }
  1967.     catch (e) {
  1968.       ERROR("safeInstallOperation: failed to remove the folder we failed to install " +
  1969.             "an item into: " + itemLocation.path + " -- There is not much to suggest " +
  1970.             "here... maybe restart and try again?");
  1971.       cleanUpTrash(itemLocationTrash);
  1972.       throw e;
  1973.     }
  1974.     rollbackMove();
  1975.     cleanUpTrash(itemLocationTrash);
  1976.     throw e;
  1977.   }
  1978.  
  1979.   // Now, and only now - after everything else has succeeded (against all odds!)
  1980.   // remove the {GUID}-trash directory where we stashed the old version of the
  1981.   // item.
  1982.   cleanUpTrash(itemLocationTrash);
  1983. }
  1984.  
  1985. /**
  1986.  * Manages the list of pending operations.
  1987.  */
  1988. var PendingOperations = {
  1989.   _ops: { },
  1990.  
  1991.   /**
  1992.    * Adds an entry to the Pending Operations List
  1993.    * @param   opType
  1994.    *          The type of Operation to be performed
  1995.    * @param   entry
  1996.    *          A JS Object representing the item to be operated on:
  1997.    *          "locationKey"   The name of the Install Location where the item
  1998.    *                          is installed.
  1999.    *          "id"            The GUID of the item.
  2000.    */
  2001.   addItem: function PendingOperations_addItem(opType, entry) {
  2002.     if (opType == OP_NONE)
  2003.       this.clearOpsForItem(entry.id);
  2004.     else {
  2005.       if (!(opType in this._ops))
  2006.         this._ops[opType] = { };
  2007.       this._ops[opType][entry.id] = entry.locationKey;
  2008.     }
  2009.   },
  2010.  
  2011.   /**
  2012.    * Removes a Pending Operation from the list
  2013.    * @param   opType
  2014.    *          The type of Operation being removed
  2015.    * @param   id
  2016.    *          The GUID of the item to remove the entry for
  2017.    */
  2018.   clearItem: function PendingOperations_clearItem(opType, id) {
  2019.     if (opType in this._ops && id in this._ops[opType])
  2020.       delete this._ops[opType][id];
  2021.   },
  2022.  
  2023.   /**
  2024.    * Removes all Pending Operation for an item
  2025.    * @param   id
  2026.    *          The ID of the item to remove the entries for
  2027.    */
  2028.   clearOpsForItem: function PendingOperations_clearOpsForItem(id) {
  2029.     for (var opType in this._ops) {
  2030.       if (id in this._ops[opType])
  2031.         delete this._ops[opType][id];
  2032.     }
  2033.   },
  2034.  
  2035.   /**
  2036.    * Remove all Pending Operations of a certain type
  2037.    * @param   opType
  2038.    *          The type of Operation to remove all entries for
  2039.    */
  2040.   clearItems: function PendingOperations_clearItems(opType) {
  2041.     if (opType in this._ops)
  2042.       delete this._ops[opType];
  2043.   },
  2044.  
  2045.   /**
  2046.    * Get an array of operations of a certain type
  2047.    * @param   opType
  2048.    *          The type of Operation to return a list of
  2049.    */
  2050.   getOperations: function PendingOperations_getOperations(opType) {
  2051.     if (!(opType in this._ops))
  2052.       return [];
  2053.     var ops = [];
  2054.     for (var id in this._ops[opType])
  2055.       ops.push( {id: id, locationKey: this._ops[opType][id] } );
  2056.     return ops;
  2057.   },
  2058.  
  2059.   /**
  2060.    * The total number of Pending Operations, for all types.
  2061.    */
  2062.   get size() {
  2063.     var size = 0;
  2064.     for (var opType in this._ops) {
  2065.       for (var id in this._ops[opType])
  2066.         ++size;
  2067.     }
  2068.     return size;
  2069.   }
  2070. };
  2071.  
  2072. /**
  2073.  * Manages registered Install Locations
  2074.  */
  2075. var InstallLocations = {
  2076.   _locations: { },
  2077.  
  2078.   /**
  2079.    * A nsISimpleEnumerator of all available Install Locations.
  2080.    */
  2081.   get enumeration() {
  2082.     var installLocations = [];
  2083.     for (var key in this._locations)
  2084.       installLocations.push(InstallLocations.get(key));
  2085.     return new ArrayEnumerator(installLocations);
  2086.   },
  2087.  
  2088.   /**
  2089.    * Gets a named Install Location
  2090.    * @param   name
  2091.    *          The name of the Install Location to get
  2092.    */
  2093.   get: function InstallLocations_get(name) {
  2094.     return name in this._locations ? this._locations[name] : null;
  2095.   },
  2096.  
  2097.   /**
  2098.    * Registers an Install Location
  2099.    * @param   installLocation
  2100.    *          The Install Location to register
  2101.    */
  2102.   put: function InstallLocations_put(installLocation) {
  2103.     this._locations[installLocation.name] = installLocation;
  2104.   }
  2105. };
  2106.  
  2107. /**
  2108.  * Manages the Startup Cache. The Startup Cache is a representation
  2109.  * of the contents of extensions.cache, a list of all
  2110.  * items the Extension System knows about, whether or not they
  2111.  * are active or visible.
  2112.  */
  2113. var StartupCache = {
  2114.   /**
  2115.    * Location Name -> GUID hash of entries from the Startup Cache file
  2116.    * Each entry has the following properties:
  2117.    *  "descriptor"    The location on disk of the item
  2118.    *  "mtime"         The time the location was last modified
  2119.    *  "op"            Any pending operations on this item.
  2120.    *  "location"      The Install Location name where the item is installed.
  2121.    */
  2122.   entries: { },
  2123.  
  2124.   /**
  2125.    * Puts an entry into the Startup Cache
  2126.    * @param   installLocation
  2127.    *          The Install Location where the item is installed
  2128.    * @param   id
  2129.    *          The GUID of the item
  2130.    * @param   op
  2131.    *          The name of the operation to be performed
  2132.    * @param   shouldCreate
  2133.    *          Whether or not we should create a new entry for this item
  2134.    *          in the cache if one does not already exist.
  2135.    */
  2136.   put: function StartupCache_put(installLocation, id, op, shouldCreate) {
  2137.     var itemLocation = installLocation.getItemLocation(id);
  2138.  
  2139.     var descriptor = null;
  2140.     var mtime = null;
  2141.     if (itemLocation) {
  2142.       itemLocation.QueryInterface(Ci.nsILocalFile);
  2143.       descriptor = getDescriptorFromFile(itemLocation, installLocation);
  2144.       if (itemLocation.exists() && itemLocation.isDirectory())
  2145.         mtime = Math.floor(itemLocation.lastModifiedTime / 1000);
  2146.     }
  2147.  
  2148.     this._putRaw(installLocation.name, id, descriptor, mtime, op, shouldCreate);
  2149.   },
  2150.  
  2151.   /**
  2152.    * Private helper function for putting an entry into the Startup Cache
  2153.    * without relying on the presence of its associated nsIInstallLocation
  2154.    * instance.
  2155.    *
  2156.    * @param key
  2157.    *        The install location name.
  2158.    * @param id
  2159.    *        The ID of the item.
  2160.    * @param descriptor
  2161.    *        Value returned from absoluteDescriptor.  May be null, in which
  2162.    *        case the descriptor field is not updated.
  2163.    * @param mtime
  2164.    *        The last modified time of the item.  May be null, in which case the
  2165.    *        descriptor field is not updated.
  2166.    * @param op
  2167.    *        The OP code to store with the entry.
  2168.    * @param shouldCreate
  2169.    *        Boolean value indicating whether to create or delete the entry.
  2170.    */
  2171.   _putRaw: function StartupCache__putRaw(key, id, descriptor, mtime, op, shouldCreate) {
  2172.     if (!(key in this.entries))
  2173.       this.entries[key] = { };
  2174.     if (!(id in this.entries[key]))
  2175.       this.entries[key][id] = { };
  2176.     if (shouldCreate) {
  2177.       if (!this.entries[key][id])
  2178.         this.entries[key][id] = { };
  2179.  
  2180.       var entry = this.entries[key][id];
  2181.  
  2182.       if (descriptor)
  2183.         entry.descriptor = descriptor;
  2184.       if (mtime)
  2185.         entry.mtime = mtime;
  2186.       entry.op = op;
  2187.       entry.location = key;
  2188.     }
  2189.     else
  2190.       this.entries[key][id] = null;
  2191.   },
  2192.  
  2193.   /**
  2194.    * Clears an entry from the Startup Cache
  2195.    * @param   installLocation
  2196.    *          The Install Location where item is installed
  2197.    * @param   id
  2198.    *          The GUID of the item.
  2199.    */
  2200.   clearEntry: function StartupCache_clearEntry(installLocation, id) {
  2201.     var key = installLocation.name;
  2202.     if (key in this.entries && id in this.entries[key])
  2203.       this.entries[key][id] = null;
  2204.   },
  2205.  
  2206.   /**
  2207.    * Get all the startup cache entries for a particular ID.
  2208.    * @param   id
  2209.    *          The GUID of the item to locate.
  2210.    * @returns An array of Startup Cache entries for the specified ID.
  2211.    */
  2212.   findEntries: function StartupCache_findEntries(id) {
  2213.     var entries = [];
  2214.     for (var key in this.entries) {
  2215.       if (id in this.entries[key])
  2216.         entries.push(this.entries[key][id]);
  2217.     }
  2218.     return entries;
  2219.   },
  2220.  
  2221.   /**
  2222.    * Read the Item-Change manifest file into a hash of properties.
  2223.    * The Item-Change manifest currently holds a list of paths, with the last
  2224.    * mtime for each path, and the GUID of the item at that path.
  2225.    */
  2226.   read: function StartupCache_read() {
  2227.     var itemChangeManifest = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2228.     if (!itemChangeManifest.exists()) {
  2229.       // There is no change manifest for some reason, either we're in an initial
  2230.       // state or something went wrong with one of the other files and the
  2231.       // change manifest was removed. Return an empty dataset and rebuild.
  2232.       gFirstRun = true;
  2233.       return;
  2234.     }
  2235.     var fis = Cc["@mozilla.org/network/file-input-stream;1"].
  2236.               createInstance(Ci.nsIFileInputStream);
  2237.     fis.init(itemChangeManifest, -1, -1, false);
  2238.     if (fis instanceof Ci.nsILineInputStream) {
  2239.       var line = { value: "" };
  2240.       var more = false;
  2241.       do {
  2242.         more = fis.readLine(line);
  2243.         if (line.value) {
  2244.           // The Item-Change manifest is formatted like so:
  2245.           //  (pd = descriptor)
  2246.           // location-key\tguid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  2247.           // location-key\tguid-of-item\tpd-to-extension2\tmtime-of-pd\tpending-op
  2248.           // ...
  2249.           // We hash on location-key first, because we don't want to have to
  2250.           // spin up the main extensions datasource on every start to determine
  2251.           // the Install Location for an item.
  2252.           // We hash on guid second, because we want a way to quickly determine
  2253.           // item GUID during a check loop that runs on every startup.
  2254.           var parts = line.value.split("\t");
  2255.           // Silently drop any entries in unknown install locations
  2256.           if (!InstallLocations.get(parts[0]))
  2257.             continue;
  2258.           var op = parts[4];
  2259.           this._putRaw(parts[0], parts[1], parts[2], parts[3], op, true);
  2260.           if (op)
  2261.             PendingOperations.addItem(op, { locationKey: parts[0], id: parts[1] });
  2262.         }
  2263.       }
  2264.       while (more);
  2265.     }
  2266.     fis.close();
  2267.   },
  2268.  
  2269.   /**
  2270.    * Writes the Startup Cache to disk
  2271.    */
  2272.   write: function StartupCache_write() {
  2273.     var extensionsCacheFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2274.     var fos = openSafeFileOutputStream(extensionsCacheFile);
  2275.     for (var locationKey in this.entries) {
  2276.       for (var id in this.entries[locationKey]) {
  2277.         var entry = this.entries[locationKey][id];
  2278.         if (entry) {
  2279.           try {
  2280.             var itemLocation = getFileFromDescriptor(entry.descriptor, InstallLocations.get(locationKey));
  2281.  
  2282.             // Update our knowledge of this item's last-modified-time.
  2283.             // XXXdarin: this may cause us to miss changes in some cases.
  2284.             var itemMTime = 0;
  2285.             if (itemLocation.exists() && itemLocation.isDirectory())
  2286.               itemMTime = Math.floor(itemLocation.lastModifiedTime / 1000);
  2287.  
  2288.             // Each line in the startup cache manifest is in this form:
  2289.             // location-key\tid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  2290.             var line = locationKey + "\t" + id + "\t" + entry.descriptor + "\t" +
  2291.                        itemMTime + "\t" + entry.op + "\r\n";
  2292.             fos.write(line, line.length);
  2293.           }
  2294.           catch (e) {}
  2295.         }
  2296.       }
  2297.     }
  2298.     closeSafeFileOutputStream(fos);
  2299.   }
  2300. };
  2301.  
  2302. /**
  2303.  * Installs, manages and tracks compatibility for Extensions and Themes
  2304.  * @constructor
  2305.  */
  2306. function ExtensionManager() {
  2307.   gApp = Cc["@mozilla.org/xre/app-info;1"].
  2308.          getService(Ci.nsIXULAppInfo).QueryInterface(Ci.nsIXULRuntime);
  2309.   gOSTarget = gApp.OS;
  2310.   try {
  2311.     gXPCOMABI = gApp.XPCOMABI;
  2312.   } catch (ex) {
  2313.     // Provide a default for gXPCOMABI. It won't be compared to an
  2314.     // item's metadata (i.e. install.rdf can't specify e.g. WINNT_unknownABI
  2315.     // as targetPlatform), but it will be displayed in error messages and
  2316.     // transmitted to update URLs.
  2317.     gXPCOMABI = UNKNOWN_XPCOM_ABI;
  2318.   }
  2319.   gPref = Cc["@mozilla.org/preferences-service;1"].
  2320.           getService(Ci.nsIPrefBranch2);
  2321.  
  2322.   gOS = Cc["@mozilla.org/observer-service;1"].
  2323.         getService(Ci.nsIObserverService);
  2324.   gOS.addObserver(this, "xpcom-shutdown", false);
  2325.  
  2326.   gConsole = Cc["@mozilla.org/consoleservice;1"].
  2327.              getService(Ci.nsIConsoleService);
  2328.  
  2329.   gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
  2330.          getService(Ci.nsIRDFService);
  2331.   gInstallManifestRoot = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
  2332.  
  2333.   // Register Global GRE Install Location
  2334.   var greGlobalExtensions = getDirNoCreate(KEY_GREDIR, [DIR_EXTENSIONS]);
  2335.   var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL;
  2336.   var greGlobalLocation = new DirectoryInstallLocation(KEY_GRE_GLOBAL,
  2337.                                                        greGlobalExtensions, true,
  2338.                                                        priority);
  2339.   InstallLocations.put(greGlobalLocation);
  2340.  
  2341.   // Register Global Application Install Location
  2342.   var appGlobalExtensions = getDirNoCreate(KEY_APPDIR, [DIR_EXTENSIONS]);
  2343.   var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL;
  2344.   var globalLocation = new DirectoryInstallLocation(KEY_APP_GLOBAL,
  2345.                                                     appGlobalExtensions, true,
  2346.                                                     priority, false);
  2347.   InstallLocations.put(globalLocation);
  2348.  
  2349.   // Register App-Profile Install Location
  2350.   var appProfileExtensions = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS]);
  2351.   var priority = Ci.nsIInstallLocation.PRIORITY_APP_PROFILE;
  2352.   var profileLocation = new DirectoryInstallLocation(KEY_APP_PROFILE,
  2353.                                                      appProfileExtensions, false,
  2354.                                                      priority, false);
  2355.   InstallLocations.put(profileLocation);
  2356.  
  2357.   // Register per-user Install Location
  2358.   try {
  2359.     var appSystemUExtensions = getDirNoCreate("XREUSysExt", [gApp.ID]);
  2360.   }
  2361.   catch(e) { }
  2362.  
  2363.   if (appSystemUExtensions) {
  2364.     var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_USER;
  2365.     var systemLocation = new DirectoryInstallLocation(KEY_APP_SYSTEM_USER,
  2366.                                                       appSystemUExtensions, false,
  2367.                                                       priority, true);
  2368.  
  2369.     InstallLocations.put(systemLocation);
  2370.   }
  2371.  
  2372.   // Register App-System-Shared Install Location
  2373.   try {
  2374.     var appSystemSExtensions = getDirNoCreate("XRESysSExtPD", [gApp.ID]);
  2375.   }
  2376.   catch (e) { }
  2377.  
  2378.   if (appSystemSExtensions) {
  2379.     var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 10;
  2380.     var systemLocation = new DirectoryInstallLocation(KEY_APP_SYSTEM_SHARE,
  2381.                                                       appSystemSExtensions, true,
  2382.                                                       priority, true);
  2383.     InstallLocations.put(systemLocation);
  2384.   }
  2385.  
  2386.   // Register App-System-Local Install Location
  2387.   try {
  2388.     var appSystemLExtensions = getDirNoCreate("XRESysLExtPD", [gApp.ID]);
  2389.   }
  2390.   catch (e) { }
  2391.  
  2392.   if (appSystemLExtensions) {
  2393.     var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 20;
  2394.     var systemLocation = new DirectoryInstallLocation(KEY_APP_SYSTEM_LOCAL,
  2395.                                                       appSystemLExtensions, true,
  2396.                                                       priority, true);
  2397.     InstallLocations.put(systemLocation);
  2398.   }
  2399.  
  2400. //@line 2523 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  2401.  
  2402.   // Register Additional Install Locations
  2403.   var categoryManager = Cc["@mozilla.org/categorymanager;1"].
  2404.                         getService(Ci.nsICategoryManager);
  2405.   var locations = categoryManager.enumerateCategory(CATEGORY_INSTALL_LOCATIONS);
  2406.   while (locations.hasMoreElements()) {
  2407.     var entry = locations.getNext().QueryInterface(Ci.nsISupportsCString).data;
  2408.     var contractID = categoryManager.getCategoryEntry(CATEGORY_INSTALL_LOCATIONS, entry);
  2409.     var location = Cc[contractID].getService(Ci.nsIInstallLocation);
  2410.     InstallLocations.put(location);
  2411.   }
  2412. }
  2413.  
  2414. ExtensionManager.prototype = {
  2415.   /**
  2416.    * See nsIObserver.idl
  2417.    */
  2418.   observe: function EM_observe(subject, topic, data) {
  2419.     switch (topic) {
  2420.     case "profile-after-change":
  2421.       this._profileSelected();
  2422.       break;
  2423.     case "quit-application-requested":
  2424.       this._confirmCancelDownloadsOnQuit(subject);
  2425.       break;
  2426.     case "offline-requested":
  2427.       this._confirmCancelDownloadsOnOffline(subject);
  2428.       break;
  2429.     case "xpcom-shutdown":
  2430.       this._shutdown();
  2431.       break;
  2432.     case "nsPref:changed":
  2433.       if (data == PREF_EM_LOGGING_ENABLED)
  2434.         this._loggingToggled();
  2435.       else if (data == PREF_EM_CHECK_COMPATIBILITY ||
  2436.                data == PREF_EM_CHECK_UPDATE_SECURITY)
  2437.         this._updateAppDisabledState();
  2438.       else if ((data == PREF_MATCH_OS_LOCALE) || (data == PREF_SELECTED_LOCALE))
  2439.         this._updateLocale();
  2440.       break;
  2441.     }
  2442.   },
  2443.  
  2444.   /**
  2445.    * Refresh the logging enabled global from preferences when the user changes
  2446.    * the preference settting.
  2447.    */
  2448.   _loggingToggled: function EM__loggingToggled() {
  2449.     gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2450.   },
  2451.  
  2452.   /**
  2453.    * Retrieves the current locale
  2454.    */
  2455.   _updateLocale: function EM__updateLocale() {
  2456.     try {
  2457.       if (gPref.getBoolPref(PREF_MATCH_OS_LOCALE)) {
  2458.         var localeSvc = Cc["@mozilla.org/intl/nslocaleservice;1"].
  2459.                         getService(Ci.nsILocaleService);
  2460.         gLocale = localeSvc.getLocaleComponentForUserAgent();
  2461.         return;
  2462.       }
  2463.     }
  2464.     catch (ex) {
  2465.     }
  2466.     gLocale = gPref.getCharPref(PREF_SELECTED_LOCALE);
  2467.   },
  2468.  
  2469.   /**
  2470.    * When a preference is toggled that affects whether an item is usable or not
  2471.    * we must app-enable or app-disable the item based on the new settings.
  2472.    */
  2473.   _updateAppDisabledState: function EM__updateAppDisabledState() {
  2474.     gCheckCompatibility = getPref("getBoolPref", PREF_EM_CHECK_COMPATIBILITY, true);
  2475.     gCheckUpdateSecurity = getPref("getBoolPref", PREF_EM_CHECK_UPDATE_SECURITY, true);
  2476.     var ds = this.datasource;
  2477.  
  2478.     // Enumerate all items
  2479.     var ctr = getContainer(ds, ds._itemRoot);
  2480.     var elements = ctr.GetElements();
  2481.     while (elements.hasMoreElements()) {
  2482.       var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource);
  2483.  
  2484.       // App disable or enable items as necessary
  2485.       // _appEnableItem and _appDisableItem will do nothing if the item is already
  2486.       // in the right state.
  2487.       id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
  2488.       if (this._isUsableItem(id))
  2489.         this._appEnableItem(id);
  2490.       else
  2491.         this._appDisableItem(id);
  2492.     }
  2493.   },
  2494.  
  2495.   /**
  2496.    * Initialize the system after a profile has been selected.
  2497.    */
  2498.   _profileSelected: function EM__profileSelected() {
  2499.     // Tell the Chrome Registry which Skin to select
  2500.     try {
  2501.       if (gPref.getBoolPref(PREF_DSS_SWITCHPENDING)) {
  2502.         var toSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  2503.         gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, toSelect);
  2504.         gPref.clearUserPref(PREF_DSS_SWITCHPENDING);
  2505.         gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
  2506.       }
  2507.     }
  2508.     catch (e) {
  2509.     }
  2510.  
  2511.     if ("nsICrashReporter" in Ci && gApp instanceof Ci.nsICrashReporter) {
  2512.       // Annotate the crash report with the list of add-ons
  2513.       try {
  2514.         try {
  2515.           gApp.annotateCrashReport("Add-ons", gPref.getCharPref(PREF_EM_ENABLED_ITEMS));
  2516.         } catch (e) { }
  2517.         gApp.annotateCrashReport("Theme", gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN));
  2518.       }
  2519.       catch (ex) {
  2520.         // This will fail in unnofficial builds, ignorable error
  2521.       }
  2522.     }
  2523.  
  2524.     gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2525.     gCheckCompatibility = getPref("getBoolPref", PREF_EM_CHECK_COMPATIBILITY, true);
  2526.     gCheckUpdateSecurity = getPref("getBoolPref", PREF_EM_CHECK_UPDATE_SECURITY, true);
  2527.     gPref.addObserver("extensions.", this, false);
  2528.     gPref.addObserver(PREF_MATCH_OS_LOCALE, this, false);
  2529.     gPref.addObserver(PREF_SELECTED_LOCALE, this, false);
  2530.     this._updateLocale();
  2531.   },
  2532.  
  2533.   /**
  2534.    * Notify user that there are new addons updates
  2535.    */
  2536.   _showUpdatesWindow: function EM__showUpdatesWindow() {
  2537.     if (!getPref("getBoolPref", PREF_UPDATE_NOTIFYUSER, false))
  2538.       return;
  2539.  
  2540.     const EMURL = "chrome://mozapps/content/extensions/extensions.xul";
  2541.     const EMFEATURES = "chrome,centerscreen,extra-chrome,dialog,resizable,modal";
  2542.  
  2543.     var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  2544.              getService(Ci.nsIWindowWatcher);
  2545.     var param = Cc["@mozilla.org/supports-array;1"].
  2546.                 createInstance(Ci.nsISupportsArray);
  2547.     var arg = Cc["@mozilla.org/supports-string;1"].
  2548.               createInstance(Ci.nsISupportsString);
  2549.     arg.data = "updates-only";
  2550.     param.AppendElement(arg);
  2551.     ww.openWindow(null, EMURL, null, EMFEATURES, param);
  2552.   },
  2553.  
  2554.   /**
  2555.    * Clean up on application shutdown to avoid leaks.
  2556.    */
  2557.   _shutdown: function EM__shutdown() {
  2558.     if (!gAllowFlush) {
  2559.       // Something went wrong and there are potentially flushes pending.
  2560.       ERROR("Reached _shutdown and without clearing any pending flushes");
  2561.       try {
  2562.         gAllowFlush = true;
  2563.         if (gManifestNeedsFlush) {
  2564.           gManifestNeedsFlush = false;
  2565.           this._updateManifests(false);
  2566.         }
  2567.         if (gDSNeedsFlush) {
  2568.           gDSNeedsFlush = false;
  2569.           this.datasource.Flush();
  2570.         }
  2571.       }
  2572.       catch (e) {
  2573.         ERROR("Error flushing caches: " + e);
  2574.       }
  2575.     }
  2576.  
  2577.     gOS.removeObserver(this, "xpcom-shutdown");
  2578.  
  2579.     // Release strongly held services.
  2580.     gOS = null;
  2581.     if (this._ds) {
  2582.       gRDF.UnregisterDataSource(this._ptr);
  2583.       this._ptr = null;
  2584.       this._ds.shutdown();
  2585.       this._ds = null;
  2586.     }
  2587.     gRDF = null;
  2588.     if (gPref) {
  2589.       gPref.removeObserver("extensions.", this);
  2590.       gPref.removeObserver(PREF_MATCH_OS_LOCALE, this);
  2591.       gPref.removeObserver(PREF_SELECTED_LOCALE, this);
  2592.     }
  2593.     gPref = null;
  2594.     gConsole = null;
  2595.     gVersionChecker = null;
  2596.     gDirService = null;
  2597.     gInstallManifestRoot = null;
  2598.     gApp = null;
  2599.   },
  2600.  
  2601.   /**
  2602.    * Check for presence of critical Extension system files. If any is missing,
  2603.    * delete the others and signal that the system needs to rebuild them all
  2604.    * from scratch.
  2605.    * @returns true if any critical file is missing and the system needs to
  2606.    *          be rebuilt, false otherwise.
  2607.    */
  2608.   _ensureDatasetIntegrity: function EM__ensureDatasetIntegrity() {
  2609.     var profD = getDirNoCreate(KEY_PROFILEDIR, []);
  2610.     var extensionsDS = profD.clone();
  2611.     extensionsDS.append(FILE_EXTENSIONS);
  2612.     var extensionsINI = profD.clone();
  2613.     extensionsINI.append(FILE_EXTENSION_MANIFEST);
  2614.     var extensionsCache = profD;
  2615.     extensionsCache.append(FILE_EXTENSIONS_STARTUP_CACHE);
  2616.  
  2617.     var dsExists = extensionsDS.exists();
  2618.     var iniExists = extensionsINI.exists();
  2619.     var cacheExists = extensionsCache.exists();
  2620.  
  2621.     if (dsExists && iniExists && cacheExists)
  2622.       return [false, !iniExists];
  2623.  
  2624.     // If any of the files are missing, remove the .ini file
  2625.     if (iniExists)
  2626.       extensionsINI.remove(false);
  2627.  
  2628.     // If the extensions datasource is missing remove the .cache file if it exists
  2629.     if (!dsExists && cacheExists)
  2630.       extensionsCache.remove(false);
  2631.  
  2632.     return [true, !iniExists];
  2633.   },
  2634.  
  2635.   /**
  2636.    * See nsIExtensionManager.idl
  2637.    */
  2638.   start: function EM_start(commandLine) {
  2639.     var isDirty, forceAutoReg;
  2640.  
  2641.     // Check for missing manifests - e.g. missing extensions.ini, missing
  2642.     // extensions.cache, extensions.rdf etc. If any of these files
  2643.     // is missing then we are in some kind of weird or initial state and need
  2644.     // to force a regeneration.
  2645.     [isDirty, forceAutoReg] = this._ensureDatasetIntegrity();
  2646.  
  2647.     // Block attempts to flush for the entire startup
  2648.     gAllowFlush = false;
  2649.  
  2650.     // Configure any items that are being installed, uninstalled or upgraded
  2651.     // by being added, removed or modified by another process. We must do this
  2652.     // on every startup since there is no way we can tell if this has happened
  2653.     // or not!
  2654.     if (this._checkForFileChanges())
  2655.       isDirty = true;
  2656.  
  2657.     this._showUpdatesWindow();
  2658.  
  2659.     if (PendingOperations.size != 0)
  2660.       isDirty = true;
  2661.  
  2662.     var needsRestart = false;
  2663.     // Extension Changes
  2664.     if (isDirty) {
  2665.       needsRestart = this._finishOperations();
  2666.  
  2667.       if (forceAutoReg) {
  2668.         this._extensionListChanged = true;
  2669.         needsRestart = true;
  2670.       }
  2671.     }
  2672.  
  2673.     // Resume flushing and perform a flush for anything that was deferred
  2674.     try {
  2675.       gAllowFlush = true;
  2676.       if (gManifestNeedsFlush) {
  2677.         gManifestNeedsFlush = false;
  2678.         this._updateManifests(false);
  2679.       }
  2680.       if (gDSNeedsFlush) {
  2681.         gDSNeedsFlush = false;
  2682.         this.datasource.Flush();
  2683.       }
  2684.     }
  2685.     catch (e) {
  2686.       ERROR("Error flushing caches: " + e);
  2687.     }
  2688.  
  2689.     if (!needsRestart)
  2690.       this._startTimers();
  2691.  
  2692.     return needsRestart;
  2693.   },
  2694.  
  2695.   /**
  2696.    * Begins all background update check timers
  2697.    */
  2698.   _startTimers: function EM__startTimers() {
  2699.     // Register a background update check timer
  2700.     var tm = Cc["@mozilla.org/updates/timer-manager;1"].
  2701.              getService(Ci.nsIUpdateTimerManager);
  2702.     var interval = getPref("getIntPref", PREF_EM_UPDATE_INTERVAL, 86400);
  2703.     tm.registerTimer("addon-background-update-timer", this, interval);
  2704.   },
  2705.  
  2706.   /**
  2707.    * Notified when a timer fires
  2708.    * @param   timer
  2709.    *          The timer that fired
  2710.    */
  2711.   notify: function EM_notify(timer) {
  2712.     if (!getPref("getBoolPref", PREF_EM_UPDATE_ENABLED, true))
  2713.       return;
  2714.  
  2715.     var items = this.getItemList(Ci.nsIUpdateItem.TYPE_ANY, { });
  2716.  
  2717.     var updater = new ExtensionItemUpdater(this);
  2718.     updater.checkForUpdates(items, items.length,
  2719.                             Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION,
  2720.                             new BackgroundUpdateCheckListener(this.datasource),
  2721.                             null, null);
  2722.   },
  2723.  
  2724.   /**
  2725.    * See nsIExtensionManager.idl
  2726.    */
  2727.   handleCommandLineArgs: function EM_handleCommandLineArgs(commandLine) {
  2728.     try {
  2729.       var globalExtension = commandLine.handleFlagWithParam("install-global-extension", false);
  2730.       if (globalExtension) {
  2731.         var file = commandLine.resolveFile(globalExtension);
  2732.         this._installGlobalItem(file);
  2733.       }
  2734.       var globalTheme = commandLine.handleFlagWithParam("install-global-theme", false);
  2735.       if (globalTheme) {
  2736.         file = commandLine.resolveFile(globalTheme);
  2737.         this._installGlobalItem(file);
  2738.       }
  2739.     }
  2740.     catch (e) {
  2741.       ERROR("ExtensionManager:handleCommandLineArgs - failure, catching exception - lineno: " +
  2742.             e.lineNumber + " - file: " + e.fileName + " - " + e);
  2743.     }
  2744.     commandLine.preventDefault = true;
  2745.   },
  2746.  
  2747.   /**
  2748.    * Installs an XPI/JAR file into the KEY_APP_GLOBAL install location.
  2749.    * @param   file
  2750.    *          The XPI/JAR file to extract
  2751.    */
  2752.   _installGlobalItem: function EM__installGlobalItem(file) {
  2753.     if (!file || !file.exists())
  2754.       throw new Error("Unable to find the file specified on the command line!");
  2755. //@line 2883 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  2756.     var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  2757.     if (!installManifestFile.exists())
  2758.       throw new Error("The package is missing an install manifest!");
  2759.     var installManifest = getInstallManifest(installManifestFile);
  2760.     installManifestFile.remove(false);
  2761.     var installData = this._getInstallData(installManifest);
  2762.     var installer = new Installer(installManifest, installData.id,
  2763.                                   InstallLocations.get(KEY_APP_GLOBAL),
  2764.                                   installData.type);
  2765.     installer._installExtensionFiles(file);
  2766.     if (installData.type == Ci.nsIUpdateItem.TYPE_THEME)
  2767.       installer.upgradeThemeChrome();
  2768.     else
  2769.       installer.upgradeExtensionChrome();
  2770.   },
  2771.  
  2772.   /**
  2773.    * Check to see if a file is a XPI/JAR file that the user dropped into this
  2774.    * Install Location. (i.e. a XPI that is not a staged XPI from an install
  2775.    * transaction that is currently in operation).
  2776.    * @param   file
  2777.    *          The XPI/JAR file to configure
  2778.    * @param   location
  2779.    *          The Install Location where this file was found.
  2780.    * @returns A nsIUpdateItem representing the dropped XPI if this file was a
  2781.    *          XPI/JAR that needs installation, null otherwise.
  2782.    */
  2783.   _getItemForDroppedFile: function EM__getItemForDroppedFile(file, location) {
  2784.     if (fileIsItemPackage(file)) {
  2785.       // We know nothing about this item, it is not something we've
  2786.       // staged in preparation for finalization, so assume it's something
  2787.       // the user dropped in.
  2788.       LOG("A Item Package appeared at: " + file.path + " that we know " +
  2789.           "nothing about, assuming it was dropped in by the user and " +
  2790.           "configuring for installation now. Location Key: " + location.name);
  2791.  
  2792.       var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  2793.       if (!installManifestFile.exists())
  2794.         return null;
  2795.       var installManifest = getInstallManifest(installManifestFile);
  2796.       installManifestFile.remove(false);
  2797.       var ds = this.datasource;
  2798.       var installData = this._getInstallData(installManifest);
  2799.       var targetAppInfo = ds.getTargetApplicationInfo(installData.id, installManifest);
  2800.       return makeItem(installData.id,
  2801.                       installData.version,
  2802.                       location.name,
  2803.                       targetAppInfo ? targetAppInfo.minVersion : "",
  2804.                       targetAppInfo ? targetAppInfo.maxVersion : "",
  2805.                       getManifestProperty(installManifest, "name"),
  2806.                       "", /* XPI Update URL */
  2807.                       "", /* XPI Update Hash */
  2808.                       getManifestProperty(installManifest, "iconURL"),
  2809.                       getManifestProperty(installManifest, "updateURL"),
  2810.                       getManifestProperty(installManifest, "updateKey"),
  2811.                       installData.type,
  2812.                       targetAppInfo ? targetAppInfo.appID : gApp.ID);
  2813.     }
  2814.     return null;
  2815.   },
  2816.  
  2817.   /**
  2818.    * Configure an item that was installed or upgraded by another process
  2819.    * so that |_finishOperations| can properly complete processing and
  2820.    * registration.
  2821.    * As this is the only point at which we can reliably know the Install
  2822.    * Location of this item, we use this as an opportunity to:
  2823.    * 1. Check that this item is compatible with this Firefox version.
  2824.    * 2. If it is, configure the item by using the supplied callback.
  2825.    *    We do not do any special handling in the case that the item is
  2826.    *    not compatible with this version other than to simply not register
  2827.    *    it and log that fact - there is no "phone home" check for updates.
  2828.    *    It may or may not make sense to do this, but for now we'll just
  2829.    *    not register.
  2830.    * @param   id
  2831.    *          The GUID of the item to validate and configure.
  2832.    * @param   location
  2833.    *          The Install Location where this item is installed.
  2834.    * @param   callback
  2835.    *          The callback that configures the item for installation upon
  2836.    *          successful validation.
  2837.    */
  2838.    installItem: function EM_installItem(id, location, callback) {
  2839.     // As this is the only pint at which we reliably know the installation
  2840.     var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  2841.     if (installRDF.exists()) {
  2842.       LOG("Item Installed/Upgraded at Install Location: " + location.name +
  2843.           " Item ID: " + id + ", attempting to register...");
  2844.       var installManifest = getInstallManifest(installRDF);
  2845.       var installData = this._getInstallData(installManifest);
  2846.       if (installData.error == INSTALLERROR_SUCCESS) {
  2847.         LOG("... success, item is compatible");
  2848.         callback(installManifest, installData.id, location, installData.type);
  2849.       }
  2850.       else if (installData.error == INSTALLERROR_INCOMPATIBLE_VERSION) {
  2851.         LOG("... success, item installed but is not compatible");
  2852.         callback(installManifest, installData.id, location, installData.type);
  2853.         this._appDisableItem(id);
  2854.       }
  2855.       else if (installData.error == INSTALLERROR_INSECURE_UPDATE) {
  2856.         LOG("... success, item installed but does not provide updates securely");
  2857.         callback(installManifest, installData.id, location, installData.type);
  2858.         this._appDisableItem(id);
  2859.       }
  2860.       else if (installData.error == INSTALLERROR_BLOCKLISTED) {
  2861.         LOG("... success, item installed but is blocklisted");
  2862.         callback(installManifest, installData.id, location, installData.type);
  2863.         this._appDisableItem(id);
  2864.       }
  2865.       else if (installData.error == INSTALLERROR_SOFTBLOCKED) {
  2866.         LOG("... success, item installed but is soft blocked, item will be disabled");
  2867.         callback(installManifest, installData.id, location, installData.type);
  2868.         this.disableItem(id);
  2869.       }
  2870.       else {
  2871.         /**
  2872.          * Turns an error code into a message for logging
  2873.          * @param   error
  2874.          *          an Install Error code
  2875.          * @returns A string message to be logged.
  2876.          */
  2877.         function translateErrorMessage(error) {
  2878.           switch (error) {
  2879.           case INSTALLERROR_INVALID_GUID:
  2880.             return "Invalid GUID";
  2881.           case INSTALLERROR_INVALID_VERSION:
  2882.             return "Invalid Version";
  2883.           case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  2884.             return "Incompatible Platform";
  2885.           }
  2886.         }
  2887.         LOG("... failure, item is not compatible, error: " +
  2888.             translateErrorMessage(installData.error));
  2889.  
  2890.         // Add the item to the Startup Cache anyway, so we don't re-detect it
  2891.         // every time the app starts.
  2892.         StartupCache.put(location, id, OP_NONE, true);
  2893.       }
  2894.     }
  2895.   },
  2896.  
  2897.   /**
  2898.    * Check for changes to items that were made independently of the Extension
  2899.    * Manager, e.g. items were added or removed from a Install Location or items
  2900.    * in an Install Location changed.
  2901.    */
  2902.   _checkForFileChanges: function EM__checkForFileChanges() {
  2903.     var em = this;
  2904.  
  2905.     /**
  2906.      * Determines if an item can be used based on whether or not the install
  2907.      * location of the "item" has an equal or higher priority than the install
  2908.      * location where another version may live.
  2909.      * @param   id
  2910.      *          The GUID of the item being installed.
  2911.      * @param   location
  2912.      *          The location where an item is to be installed.
  2913.      * @returns true if the item can be installed at that location, false
  2914.      *          otherwise.
  2915.      */
  2916.     function canUse(id, location) {
  2917.       for (var locationKey in StartupCache.entries) {
  2918.         if (locationKey != location.name &&
  2919.             id in StartupCache.entries[locationKey]) {
  2920.           if (StartupCache.entries[locationKey][id]) {
  2921.             var oldInstallLocation = InstallLocations.get(locationKey);
  2922.             if (oldInstallLocation.priority <= location.priority)
  2923.               return false;
  2924.           }
  2925.         }
  2926.       }
  2927.       return true;
  2928.     }
  2929.  
  2930.     /**
  2931.       * Gets a Dialog Param Block loaded with a set of strings to initialize the
  2932.       * XPInstall Confirmation Dialog.
  2933.       * @param   strings
  2934.       *          An array of strings
  2935.       * @returns A nsIDialogParamBlock loaded with the strings and dialog state.
  2936.       */
  2937.     function getParamBlock(strings) {
  2938.       var dpb = Cc["@mozilla.org/embedcomp/dialogparam;1"].
  2939.                 createInstance(Ci.nsIDialogParamBlock);
  2940.       // OK and Cancel Buttons
  2941.       dpb.SetInt(0, 2);
  2942.       // Number of Strings
  2943.       dpb.SetInt(1, strings.length);
  2944.       dpb.SetNumberStrings(strings.length);
  2945.       // Add Strings
  2946.       for (var i = 0; i < strings.length; ++i)
  2947.         dpb.SetString(i, strings[i]);
  2948.  
  2949.       var supportsString = Cc["@mozilla.org/supports-string;1"].
  2950.                            createInstance(Ci.nsISupportsString);
  2951.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  2952.       supportsString.data = bundle.GetStringFromName("droppedInWarning");
  2953.       var objs = Cc["@mozilla.org/array;1"].
  2954.                  createInstance(Ci.nsIMutableArray);
  2955.       objs.appendElement(supportsString, false);
  2956.       dpb.objects = objs;
  2957.       return dpb;
  2958.     }
  2959.  
  2960.     /**
  2961.      * Installs a set of files which were dropped into an install location by
  2962.      * the user, only after user confirmation.
  2963.      * @param   droppedInFiles
  2964.      *          An array of JS objects with the following properties:
  2965.      *          "file"      The nsILocalFile where the XPI lives
  2966.      *          "location"  The Install Location where the XPI was found.
  2967.      * @param   xpinstallStrings
  2968.      *          An array of strings used to initialize the XPInstall Confirm
  2969.      *          dialog.
  2970.      */
  2971.     function installDroppedInFiles(droppedInFiles, xpinstallStrings) {
  2972.       if (droppedInFiles.length == 0)
  2973.         return;
  2974.  
  2975.       var dpb = getParamBlock(xpinstallStrings);
  2976.       var ifptr = Cc["@mozilla.org/supports-interface-pointer;1"].
  2977.                   createInstance(Ci.nsISupportsInterfacePointer);
  2978.       ifptr.data = dpb;
  2979.       ifptr.dataIID = Ci.nsIDialogParamBlock;
  2980.       var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  2981.                getService(Ci.nsIWindowWatcher);
  2982.       ww.openWindow(null, URI_XPINSTALL_CONFIRM_DIALOG,
  2983.                     "", "chrome,centerscreen,modal,dialog,titlebar", ifptr);
  2984.       if (!dpb.GetInt(0)) {
  2985.         // User said OK - install items
  2986.         for (var i = 0; i < droppedInFiles.length; ++i) {
  2987.           em.installItemFromFile(droppedInFiles[i].file,
  2988.                                  droppedInFiles[i].location.name);
  2989.           // We are responsible for cleaning up this file
  2990.           droppedInFiles[i].file.remove(false);
  2991.         }
  2992.       }
  2993.       else {
  2994.         for (i = 0; i < droppedInFiles.length; ++i) {
  2995.           // We are responsible for cleaning up this file
  2996.           droppedInFiles[i].file.remove(false);
  2997.         }
  2998.       }
  2999.     }
  3000.  
  3001.     var isDirty = false;
  3002.     var ignoreMTimeChanges = getPref("getBoolPref", PREF_EM_IGNOREMTIMECHANGES,
  3003.                                      false);
  3004.     StartupCache.read();
  3005.  
  3006.     // Array of objects with 'location' and 'id' properties to maybe install.
  3007.     var newItems = [];
  3008.  
  3009.     var droppedInFiles = [];
  3010.     var xpinstallStrings = [];
  3011.  
  3012.     // Enumerate over the install locations from low to high priority.  The
  3013.     // enumeration returned is pre-sorted.
  3014.     var installLocations = this.installLocations;
  3015.     while (installLocations.hasMoreElements()) {
  3016.       var location = installLocations.getNext().QueryInterface(Ci.nsIInstallLocation);
  3017.  
  3018.       // Hash the set of items actually held by the Install Location.
  3019.       var actualItems = { };
  3020.       var entries = location.itemLocations;
  3021.       while (true) {
  3022.         var entry = entries.nextFile;
  3023.         if (!entry)
  3024.           break;
  3025.  
  3026.         // Is this location a valid item? It must be a directory, and contain
  3027.         // an install.rdf manifest:
  3028.         if (entry.isDirectory()) {
  3029.           var installRDF = entry.clone();
  3030.           installRDF.append(FILE_INSTALL_MANIFEST);
  3031.  
  3032.           var id = location.getIDForLocation(entry);
  3033.           if (!id || (!installRDF.exists() &&
  3034.                       !location.itemIsManagedIndependently(id)))
  3035.             continue;
  3036.  
  3037.           actualItems[id] = entry;
  3038.         }
  3039.         else {
  3040.           // Check to see if this file is a XPI/JAR dropped into this dir
  3041.           // by the user, installing it if necessary. We do this here rather
  3042.           // than separately in |_finishOperations| because I don't want to
  3043.           // walk these lists multiple times on every startup.
  3044.           var item = this._getItemForDroppedFile(entry, location);
  3045.           if (item) {
  3046.             droppedInFiles.push({ file: entry, location: location });
  3047.             var prettyName = "";
  3048.             try {
  3049.               var zipReader = getZipReaderForFile(entry);
  3050.               var principal = { };
  3051.               var certPrincipal = zipReader.getCertificatePrincipal(null, principal);
  3052.               // XXXbz This string could be empty.  This needs better
  3053.               // UI to present principal.value.certificate's subject.
  3054.               prettyName = principal.value.prettyName;
  3055.             }
  3056.             catch (e) { }
  3057.             if (zipReader)
  3058.               zipReader.close();
  3059.             xpinstallStrings = xpinstallStrings.concat([item.name,
  3060.                                                         getURLSpecFromFile(entry),
  3061.                                                         item.iconURL,
  3062.                                                         prettyName]);
  3063.             isDirty = true;
  3064.           }
  3065.         }
  3066.       }
  3067.  
  3068.       if (location.name in StartupCache.entries) {
  3069.         // Look for items that have been uninstalled by removing their directory.
  3070.         for (var id in StartupCache.entries[location.name]) {
  3071.           if (!StartupCache.entries[location.name] ||
  3072.               !StartupCache.entries[location.name][id])
  3073.             continue;
  3074.  
  3075.           // Force _finishOperations to run if we have enabled or disabled items.
  3076.           // XXXdarin this should be unnecessary now that we check
  3077.           // PendingOperations.size in start()
  3078.           if (StartupCache.entries[location.name][id].op == OP_NEEDS_ENABLE ||
  3079.               StartupCache.entries[location.name][id].op == OP_NEEDS_DISABLE)
  3080.             isDirty = true;
  3081.  
  3082.           if (!(id in actualItems) &&
  3083.               StartupCache.entries[location.name][id].op != OP_NEEDS_UNINSTALL &&
  3084.               StartupCache.entries[location.name][id].op != OP_NEEDS_INSTALL &&
  3085.               StartupCache.entries[location.name][id].op != OP_NEEDS_UPGRADE) {
  3086.             // We have an entry for this id in the Extensions database, for this
  3087.             // install location, but it no longer exists in the Install Location.
  3088.             // We can infer from this that the item has been removed, so uninstall
  3089.             // it properly.
  3090.             if (canUse(id, location)) {
  3091.               LOG("Item Uninstalled via file removal from: " + StartupCache.entries[location.name][id].descriptor +
  3092.                   " Item ID: " + id + " Location Key: " + location.name + ", uninstalling item.");
  3093.  
  3094.               // Load the Extensions Datasource and force this item into the visible
  3095.               // items list if it is not already. This allows us to handle the case
  3096.               // where there is an entry for an item in the Startup Cache but not
  3097.               // in the extensions.rdf file - in that case the item will not be in
  3098.               // the visible list and calls to |getInstallLocation| will mysteriously
  3099.               // fail.
  3100.               this.datasource.updateVisibleList(id, location.name, false);
  3101.               this.uninstallItem(id);
  3102.               isDirty = true;
  3103.             }
  3104.           }
  3105.           else if (!ignoreMTimeChanges) {
  3106.             // Look for items whose mtime has changed, and as such we can assume
  3107.             // they have been "upgraded".
  3108.             var lf = { path: StartupCache.entries[location.name][id].descriptor };
  3109.             try {
  3110.                lf = getFileFromDescriptor(StartupCache.entries[location.name][id].descriptor, location);
  3111.             }
  3112.             catch (e) { }
  3113.  
  3114.             if (lf.exists && lf.exists()) {
  3115.               var actualMTime = Math.floor(lf.lastModifiedTime / 1000);
  3116.               if (actualMTime != StartupCache.entries[location.name][id].mtime) {
  3117.                 LOG("Item Location path changed: " + lf.path + " Item ID: " +
  3118.                     id + " Location Key: " + location.name + ", attempting to upgrade item...");
  3119.                 if (canUse(id, location)) {
  3120.                   this.installItem(id, location,
  3121.                                    function(installManifest, id, location, type) {
  3122.                                      em._upgradeItem(installManifest, id, location,
  3123.                                                      type);
  3124.                                    });
  3125.                   isDirty = true;
  3126.                 }
  3127.               }
  3128.             }
  3129.             else {
  3130.               isDirty = true;
  3131.               LOG("Install Location returned a missing or malformed item path! " +
  3132.                   "Item Path: " + lf.path + ", Location Key: " + location.name +
  3133.                   " Item ID: " + id);
  3134.               if (canUse(id, location)) {
  3135.                 // Load the Extensions Datasource and force this item into the visible
  3136.                 // items list if it is not already. This allows us to handle the case
  3137.                 // where there is an entry for an item in the Startup Cache but not
  3138.                 // in the extensions.rdf file - in that case the item will not be in
  3139.                 // the visible list and calls to |getInstallLocation| will mysteriously
  3140.                 // fail.
  3141.                 this.datasource.updateVisibleList(id, location.name, false);
  3142.                 this.uninstallItem(id);
  3143.               }
  3144.             }
  3145.           }
  3146.         }
  3147.       }
  3148.  
  3149.       // Look for items that have been installed by appearing in the location.
  3150.       for (var id in actualItems) {
  3151.         if (!(location.name in StartupCache.entries) ||
  3152.             !(id in StartupCache.entries[location.name]) ||
  3153.             !StartupCache.entries[location.name][id]) {
  3154.           // Remember that we've seen this item
  3155.           StartupCache.put(location, id, OP_NONE, true);
  3156.           // Push it on the stack of items to maybe install later
  3157.           newItems.push({location: location, id: id});
  3158.         }
  3159.       }
  3160.     }
  3161.  
  3162.     // Process any newly discovered items.  We do this here instead of in the
  3163.     // previous loop so that we can be sure that we have a fully populated
  3164.     // StartupCache.
  3165.     for (var i = 0; i < newItems.length; ++i) {
  3166.       var id = newItems[i].id;
  3167.       var location = newItems[i].location;
  3168.       if (canUse(id, location)) {
  3169.         LOG("Item Installed via directory addition to Install Location: " +
  3170.             location.name + " Item ID: " + id + ", attempting to register...");
  3171.         this.installItem(id, location,
  3172.                          function(installManifest, id, location, type) {
  3173.                            em._configureForthcomingItem(installManifest, id, location,
  3174.                                                         type);
  3175.                          });
  3176.         // Disable add-ons on install when the InstallDisabled file exists.
  3177.         // This is so Talkback will be disabled on a subset of installs.
  3178.         var installDisabled = location.getItemFile(id, "InstallDisabled");
  3179.         if (installDisabled.exists())
  3180.           em.disableItem(id);
  3181.         isDirty = true;
  3182.       }
  3183.     }
  3184.  
  3185.     // Ask the user if they want to install the dropped items, for security
  3186.     // purposes.
  3187.     installDroppedInFiles(droppedInFiles, xpinstallStrings);
  3188.  
  3189.     return isDirty;
  3190.   },
  3191.  
  3192.   /**
  3193.    * Upgrades contents.rdf files to chrome.manifest files for any existing
  3194.    * Extensions and Themes.
  3195.    * @returns true if actions were performed that require a restart, false
  3196.    *          otherwise.
  3197.    */
  3198.   _upgradeChrome: function EM__upgradeChrome() {
  3199.     if (inSafeMode())
  3200.       return false;
  3201.  
  3202.     var checkForNewChrome = false;
  3203.     var ds = this.datasource;
  3204.     // If we have extensions that were installed before the new flat chrome
  3205.     // manifests, and are still valid, we need to manually create the flat
  3206.     // manifest files.
  3207.     var extensions = this._getActiveItems(Ci.nsIUpdateItem.TYPE_EXTENSION +
  3208.                                           Ci.nsIUpdateItem.TYPE_LOCALE);
  3209.     for (var i = 0; i < extensions.length; ++i) {
  3210.       var e = extensions[i];
  3211.       var itemLocation = e.location.getItemLocation(e.id);
  3212.       var manifest = itemLocation.clone();
  3213.       manifest.append(FILE_CHROME_MANIFEST);
  3214.       if (!manifest.exists()) {
  3215.         var installRDF = itemLocation.clone();
  3216.         installRDF.append(FILE_INSTALL_MANIFEST);
  3217.         var installLocation = this.getInstallLocation(e.id);
  3218.         if (installLocation && installRDF.exists()) {
  3219.           var itemLocation = installLocation.getItemLocation(e.id);
  3220.           if (itemLocation.exists() && itemLocation.isDirectory()) {
  3221.             var installer = new Installer(ds, e.id, installLocation,
  3222.                                           Ci.nsIUpdateItem.TYPE_EXTENSION);
  3223.             installer.upgradeExtensionChrome();
  3224.           }
  3225.         }
  3226.         else {
  3227.           ds.removeItemMetadata(e.id);
  3228.           ds.removeItemFromContainer(e.id);
  3229.         }
  3230.  
  3231.         checkForNewChrome = true;
  3232.       }
  3233.     }
  3234.  
  3235.     var themes = this._getActiveItems(Ci.nsIUpdateItem.TYPE_THEME);
  3236.     // If we have themes that were installed before the new flat chrome
  3237.     // manifests, and are still valid, we need to manually create the flat
  3238.     // manifest files.
  3239.     for (i = 0; i < themes.length; ++i) {
  3240.       var item = themes[i];
  3241.       var itemLocation = item.location.getItemLocation(item.id);
  3242.       var manifest = itemLocation.clone();
  3243.       manifest.append(FILE_CHROME_MANIFEST);
  3244.       if (manifest.exists() ||
  3245.           item.id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  3246.         continue;
  3247.  
  3248.       var entries;
  3249.       try {
  3250.         var manifestURI = getURIFromFile(manifest);
  3251.         var chromeDir = itemLocation.clone();
  3252.         chromeDir.append(DIR_CHROME);
  3253.  
  3254.         if (!chromeDir.exists() || !chromeDir.isDirectory()) {
  3255.           ds.removeItemMetadata(item.id);
  3256.           ds.removeItemFromContainer(item.id);
  3257.           continue;
  3258.         }
  3259.  
  3260.         // We're relying on the fact that there is only one JAR file
  3261.         // in the "chrome" directory. This is a hack, but it works.
  3262.         entries = chromeDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  3263.         var jarFile = entries.nextFile;
  3264.         if (jarFile) {
  3265.           var jarFileURI = getURIFromFile(jarFile);
  3266.           var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  3267.  
  3268.           // Use the Chrome Registry API to install the theme there
  3269.           var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
  3270.                    getService(Ci.nsIToolkitChromeRegistry);
  3271.           cr.processContentsManifest(contentsURI, manifestURI, contentsURI, false, true);
  3272.         }
  3273.         entries.close();
  3274.       }
  3275.       catch (e) {
  3276.         ERROR("_upgradeChrome: failed to upgrade contents manifest for " +
  3277.               "theme: " + item.id + ", exception: " + e + "... The theme will be " +
  3278.               "disabled.");
  3279.         this._appDisableItem(item.id);
  3280.       }
  3281.       finally {
  3282.         try {
  3283.           entries.close();
  3284.         }
  3285.         catch (e) {
  3286.         }
  3287.       }
  3288.       checkForNewChrome = true;
  3289.     }
  3290.     return checkForNewChrome;
  3291.   },
  3292.  
  3293.   _checkForUncoveredItem: function EM__checkForUncoveredItem(id) {
  3294.     var ds = this.datasource;
  3295.     var oldLocation = this.getInstallLocation(id);
  3296.     var newLocations = [];
  3297.     for (var locationKey in StartupCache.entries) {
  3298.       var location = InstallLocations.get(locationKey);
  3299.       if (id in StartupCache.entries[locationKey] &&
  3300.           location.priority > oldLocation.priority)
  3301.         newLocations.push(location);
  3302.     }
  3303.     newLocations.sort(function(a, b) { return b.priority - a.priority; });
  3304.     if (newLocations.length > 0) {
  3305.       for (var i = 0; i < newLocations.length; ++i) {
  3306.         // Check to see that the item at the location exists
  3307.         var installRDF = newLocations[i].getItemFile(id, FILE_INSTALL_MANIFEST);
  3308.         if (installRDF.exists()) {
  3309.           // Update the visible item cache so that |_finalizeUpgrade| is properly
  3310.           // called from |_finishOperations|
  3311.           var name = newLocations[i].name;
  3312.           ds.updateVisibleList(id, name, true);
  3313.           PendingOperations.addItem(OP_NEEDS_UPGRADE,
  3314.                                     { locationKey: name, id: id });
  3315.           PendingOperations.addItem(OP_NEEDS_INSTALL,
  3316.                                     { locationKey: name, id: id });
  3317.           break;
  3318.         }
  3319.         else {
  3320.           // If no item exists at the location specified, remove this item
  3321.           // from the visible items list and check again.
  3322.           StartupCache.clearEntry(newLocations[i], id);
  3323.           ds.updateVisibleList(id, null, true);
  3324.         }
  3325.       }
  3326.     }
  3327.     else
  3328.       ds.updateVisibleList(id, null, true);
  3329.   },
  3330.  
  3331.   /**
  3332.    * Finish up pending operations - perform upgrades, installs, enables/disables,
  3333.    * uninstalls etc.
  3334.    * @returns true if actions were performed that require a restart, false
  3335.    *          otherwise.
  3336.    */
  3337.   _finishOperations: function EM__finishOperations() {
  3338.     try {
  3339.       // Stuff has changed, load the Extensions datasource in all its RDFey
  3340.       // glory.
  3341.       var ds = this.datasource;
  3342.       var updatedTargetAppInfos = [];
  3343.  
  3344.       var needsRestart = false;
  3345.       var upgrades = [];
  3346.       var newAddons = [];
  3347.       var addons = getPref("getCharPref", PREF_EM_NEW_ADDONS_LIST, "");
  3348.       if (addons != "")
  3349.         newAddons = addons.split(",");
  3350.       do {
  3351.         // Enable and disable during startup so items that are changed in the
  3352.         // ui can be reset to a no-op.
  3353.         // Look for extensions that need to be enabled.
  3354.         var items = PendingOperations.getOperations(OP_NEEDS_ENABLE);
  3355.         for (var i = items.length - 1; i >= 0; --i) {
  3356.           var id = items[i].id;
  3357.           var installLocation = this.getInstallLocation(id);
  3358.           StartupCache.put(installLocation, id, OP_NONE, true);
  3359.           PendingOperations.clearItem(OP_NEEDS_ENABLE, id);
  3360.           needsRestart = true;
  3361.         }
  3362.         PendingOperations.clearItems(OP_NEEDS_ENABLE);
  3363.  
  3364.         // Look for extensions that need to be disabled.
  3365.         items = PendingOperations.getOperations(OP_NEEDS_DISABLE);
  3366.         for (i = items.length - 1; i >= 0; --i) {
  3367.           id = items[i].id;
  3368.           installLocation = this.getInstallLocation(id);
  3369.           StartupCache.put(installLocation, id, OP_NONE, true);
  3370.           PendingOperations.clearItem(OP_NEEDS_DISABLE, id);
  3371.           needsRestart = true;
  3372.         }
  3373.         PendingOperations.clearItems(OP_NEEDS_DISABLE);
  3374.  
  3375.         // Look for extensions that need to be upgraded. The process here is to
  3376.         // uninstall the old version of the extension first, then install the
  3377.         // new version in its place.
  3378.         items = PendingOperations.getOperations(OP_NEEDS_UPGRADE);
  3379.         for (i = items.length - 1; i >= 0; --i) {
  3380.           id = items[i].id;
  3381.           var newLocation = InstallLocations.get(items[i].locationKey);
  3382.           // check if there is updated app compatibility info
  3383.           var newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  3384.           if (newTargetAppInfo)
  3385.             updatedTargetAppInfos.push(newTargetAppInfo);
  3386.           this._finalizeUpgrade(id, newLocation);
  3387.           upgrades.push(id);
  3388.         }
  3389.         PendingOperations.clearItems(OP_NEEDS_UPGRADE);
  3390.  
  3391.         // Install items
  3392.         items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  3393.         for (i = items.length - 1; i >= 0; --i) {
  3394.           needsRestart = true;
  3395.           id = items[i].id;
  3396.           // check if there is updated app compatibility info
  3397.           newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  3398.           if (newTargetAppInfo)
  3399.             updatedTargetAppInfos.push(newTargetAppInfo);
  3400.           this._finalizeInstall(id, null);
  3401.           if (upgrades.indexOf(id) < 0 && newAddons.indexOf(id) < 0)
  3402.             newAddons.push(id);
  3403.         }
  3404.         PendingOperations.clearItems(OP_NEEDS_INSTALL);
  3405.  
  3406.         // Look for extensions that need to be removed. This MUST be done after
  3407.         // the install operations since extensions to be installed may have to be
  3408.         // uninstalled if there are errors during the installation process!
  3409.         items = PendingOperations.getOperations(OP_NEEDS_UNINSTALL);
  3410.         for (i = items.length - 1; i >= 0; --i) {
  3411.           id = items[i].id;
  3412.           this._finalizeUninstall(id);
  3413.           this._checkForUncoveredItem(id);
  3414.           needsRestart = true;
  3415.           var pos = newAddons.indexOf(id);
  3416.           if (pos >= 0)
  3417.             newAddons.splice(pos, 1);
  3418.         }
  3419.         PendingOperations.clearItems(OP_NEEDS_UNINSTALL);
  3420.  
  3421.         // When there have been operations and all operations have completed.
  3422.         if (PendingOperations.size == 0) {
  3423.           // If there is updated app compatibility info update the datasource.
  3424.           for (i = 0; i < updatedTargetAppInfos.length; ++i)
  3425.             ds.setTargetApplicationInfo(updatedTargetAppInfos[i].id,
  3426.                                         updatedTargetAppInfos[i].targetAppID,
  3427.                                         updatedTargetAppInfos[i].minVersion,
  3428.                                         updatedTargetAppInfos[i].maxVersion,
  3429.                                         null);
  3430.  
  3431.           // Enumerate all items
  3432.           var ctr = getContainer(ds, ds._itemRoot);
  3433.           var elements = ctr.GetElements();
  3434.           while (elements.hasMoreElements()) {
  3435.             var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource);
  3436.  
  3437.             // Ensure appDisabled is in the correct state.
  3438.             id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
  3439.             if (this._isUsableItem(id))
  3440.               ds.setItemProperty(id, EM_R("appDisabled"), null);
  3441.             else
  3442.               ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  3443.  
  3444.             // userDisabled is set based on its value being OP_NEEDS_ENABLE or
  3445.             // OP_NEEDS_DISABLE. This allows us to have an item to be enabled
  3446.             // by the app and disabled by the user during a single restart.
  3447.             var value = stringData(ds.GetTarget(itemResource, EM_R("userDisabled"), true));
  3448.             if (value == OP_NEEDS_ENABLE)
  3449.               ds.setItemProperty(id, EM_R("userDisabled"), null);
  3450.             else if (value == OP_NEEDS_DISABLE)
  3451.               ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  3452.           }
  3453.         }
  3454.       }
  3455.       while (PendingOperations.size > 0);
  3456.  
  3457.       // Upgrade contents.rdf files to the new chrome.manifest format for
  3458.       // existing Extensions and Themes
  3459.       if (this._upgradeChrome()) {
  3460.         var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
  3461.                  getService(Ci.nsIChromeRegistry);
  3462.         cr.checkForNewChrome();
  3463.       }
  3464.  
  3465.       // If no additional restart is required, it implies that there are
  3466.       // no new components that need registering so we can inform the app
  3467.       // not to do any extra startup checking next time round.
  3468.       this._updateManifests(needsRestart);
  3469.  
  3470.       // Remember the list of add-ons that were installed this time around
  3471.       // unless this was a new profile.
  3472.       if (!gFirstRun && newAddons.length > 0)
  3473.         gPref.setCharPref(PREF_EM_NEW_ADDONS_LIST, newAddons.join(","));
  3474.     }
  3475.     catch (e) {
  3476.       ERROR("ExtensionManager:_finishOperations - failure, catching exception - lineno: " +
  3477.             e.lineNumber + " - file: " + e.fileName + " - " + e);
  3478.     }
  3479.     return needsRestart;
  3480.   },
  3481.  
  3482.   /**
  3483.    * Checks to see if there are items that are incompatible with this version
  3484.    * of the application, disables them to prevent incompatibility problems and
  3485.    * invokes the Update Wizard to look for newer versions.
  3486.    * @returns true if there were incompatible items installed and disabled, and
  3487.    *          the application must now be restarted to reinitialize XPCOM,
  3488.    *          false otherwise.
  3489.    */
  3490.   checkForMismatches: function EM_checkForMismatches() {
  3491.     // Check to see if the version of the application that is being started
  3492.     // now is the same one that was started last time.
  3493.     var currAppVersion = gApp.version;
  3494.     var lastAppVersion = getPref("getCharPref", PREF_EM_LAST_APP_VERSION, "");
  3495.     if (currAppVersion == lastAppVersion)
  3496.       return false;
  3497.     // With a new profile lastAppVersion doesn't exist yet.
  3498.     if (!lastAppVersion) {
  3499.       gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  3500.       return false;
  3501.     }
  3502.  
  3503.     // Block attempts to flush for the entire startup
  3504.     gAllowFlush = false;
  3505.  
  3506.     // Version mismatch, we have to load the extensions datasource and do
  3507.     // version checking. Time hit here doesn't matter since this doesn't happen
  3508.     // all that often.
  3509.     this._upgradeFromV10();
  3510.  
  3511.     // Make the extensions datasource consistent if it isn't already.
  3512.     var isDirty;
  3513.     [isDirty,] = this._ensureDatasetIntegrity();
  3514.  
  3515.     if (this._checkForFileChanges())
  3516.       isDirty = true;
  3517.  
  3518.     if (PendingOperations.size != 0)
  3519.       isDirty = true;
  3520.  
  3521.     var ds = this.datasource;
  3522.     var inactiveItemIDs = [];
  3523.     var ctr = getContainer(ds, ds._itemRoot);
  3524.     var elements = ctr.GetElements();
  3525.     while (elements.hasMoreElements()) {
  3526.       var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource);
  3527.       var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
  3528.       var appDisabled = ds.getItemProperty(id, "appDisabled");
  3529.       var userDisabled = ds.getItemProperty(id, "userDisabled")
  3530.       if (appDisabled == "true" || appDisabled == OP_NEEDS_DISABLE ||
  3531.           userDisabled == "true" || userDisabled == OP_NEEDS_DISABLE)
  3532.         inactiveItemIDs.push(id);
  3533.     }
  3534.  
  3535.     if (isDirty)
  3536.       this._finishOperations();
  3537.  
  3538.     // During app upgrade cleanup invalid entries in the extensions datasource.
  3539.     ds.beginUpdateBatch();
  3540.     var allResources = ds.GetAllResources();
  3541.     while (allResources.hasMoreElements()) {
  3542.       var res = allResources.getNext().QueryInterface(Ci.nsIRDFResource);
  3543.       if (ds.GetTarget(res, EM_R("downloadURL"), true) ||
  3544.           (!ds.GetTarget(res, EM_R("installLocation"), true) &&
  3545.           stringData(ds.GetTarget(res, EM_R("appDisabled"), true)) == "true"))
  3546.         ds.removeDownload(res.Value);
  3547.     }
  3548.     ds.endUpdateBatch();
  3549.  
  3550.     var badItems = [];
  3551.     var allAppManaged = true;
  3552.     elements = ctr.GetElements();
  3553.     while (elements.hasMoreElements()) {
  3554.       var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource);
  3555.       var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
  3556.       var location = this.getInstallLocation(id);
  3557.       if (!location) {
  3558.         // Item was in an unknown install location
  3559.         badItems.push(id);
  3560.         continue;
  3561.       }
  3562.  
  3563.       if (ds.getItemProperty(id, "appManaged") == "true") {
  3564.         // Force an update of the metadata for appManaged extensions since the
  3565.         // last modified time is not updated for directories on FAT / FAT32
  3566.         // filesystems when software update applies a new version of the app.
  3567.         if (location.name == KEY_APP_GLOBAL || location.name == KEY_GRE_GLOBAL) {
  3568.           var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  3569.           if (installRDF.exists()) {
  3570.             var metadataDS = getInstallManifest(installRDF);
  3571.             ds.addItemMetadata(id, metadataDS, location);
  3572.             ds.updateProperty(id, "compatible");
  3573.           }
  3574.         }
  3575.       }
  3576.       else if (allAppManaged)
  3577.         allAppManaged = false;
  3578.  
  3579.       var properties = {
  3580.         availableUpdateURL: null,
  3581.         availableUpdateVersion: null
  3582.       };
  3583.  
  3584.       if (ds.getItemProperty(id, "providesUpdatesSecurely") == "false") {
  3585.         /* It's possible the previous version did not understand updateKeys so
  3586.          * check if we can import one for this addon from its manifest. */
  3587.         installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  3588.         if (installRDF.exists()) {
  3589.           metadataDS = getInstallManifest(installRDF);
  3590.           var literal = metadataDS.GetTarget(gInstallManifestRoot, EM_R("updateKey"), true);
  3591.           if (literal && literal instanceof Ci.nsIRDFLiteral)
  3592.             ds.setItemProperty(id, EM_R("updateKey"), literal);
  3593.         }
  3594.       }
  3595.  
  3596.       // appDisabled is determined by an item being compatible, using secure
  3597.       // updates, satisfying its dependencies, and not being blocklisted
  3598.       if (this._isUsableItem(id)) {
  3599.         if (ds.getItemProperty(id, "appDisabled"))
  3600.           properties.appDisabled = null;
  3601.       }
  3602.       else if (!ds.getItemProperty(id, "appDisabled")) {
  3603.         properties.appDisabled = EM_L("true");
  3604.       }
  3605.  
  3606.       ds.setItemProperties(id, properties);
  3607.     }
  3608.  
  3609.     // Must clean up outside of the loop. Modifying the container while
  3610.     // iterating its contents is bad.
  3611.     for (var i = 0; i < badItems.length; i++) {
  3612.       id = badItems[i];
  3613.       LOG("Item " + id + " was installed in an unknown location, removing.");
  3614.       var disabled = ds.getItemProperty(id, "userDisabled") == "true";
  3615.       // Clean up the datasource
  3616.       ds.removeCorruptItem(id);
  3617.       // Check for any unhidden items.
  3618.       var entries = StartupCache.findEntries(id);
  3619.       if (entries.length > 0) {
  3620.         var newLocation = InstallLocations.get(entries[0].location);
  3621.         for (var j = 1; j < entries.length; j++) {
  3622.           location = InstallLocations.get(entries[j].location);
  3623.           if (newLocation.priority < location.priority)
  3624.             newLocation = location;
  3625.         }
  3626.         LOG("Activating item " + id + " in " + newLocation.name);
  3627.         var em = this;
  3628.         this.installItem(id, newLocation,
  3629.                          function(installManifest, id, location, type) {
  3630.                            em._configureForthcomingItem(installManifest, id, location,
  3631.                                                         type);
  3632.                          });
  3633.         if (disabled)
  3634.           em.disableItem(id);
  3635.       }
  3636.     }
  3637.  
  3638.     // Update the manifests to reflect the items that were disabled / enabled.
  3639.     this._updateManifests(true);
  3640.  
  3641.     // Always check for compatibility updates when upgrading if we have add-ons
  3642.     // that aren't managed by the application.
  3643.     if (!allAppManaged)
  3644.       this._showMismatchWindow(inactiveItemIDs);
  3645.  
  3646.     // Finish any pending upgrades from the compatibility update to avoid an
  3647.     // additional restart.
  3648.     if (PendingOperations.size != 0)
  3649.       this._finishOperations();
  3650.  
  3651.     // Update the last app version so we don't do this again with this version.
  3652.     gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  3653.  
  3654.     // Prevent extension update dialog from showing
  3655.     gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false);
  3656.  
  3657.     // Re-enable flushing and flush anything that was deferred
  3658.     try {
  3659.       gAllowFlush = true;
  3660.       if (gManifestNeedsFlush) {
  3661.         gManifestNeedsFlush = false;
  3662.         this._updateManifests(false);
  3663.       }
  3664.       if (gDSNeedsFlush) {
  3665.         gDSNeedsFlush = false;
  3666.         this.datasource.Flush();
  3667.       }
  3668.     }
  3669.     catch (e) {
  3670.       ERROR("Error flushing caches: " + e);
  3671.     }
  3672.  
  3673.     return true;
  3674.   },
  3675.  
  3676.   /**
  3677.    * Shows the "Compatibility Updates" UI
  3678.    * @param   items
  3679.    *          an array of item IDs that were not enabled in the previous version
  3680.    *          of the application.
  3681.    */
  3682.   _showMismatchWindow: function EM__showMismatchWindow(items) {
  3683.     var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  3684.              getService(Ci.nsIWindowMediator);
  3685.     var wizard = wm.getMostRecentWindow("Update:Wizard");
  3686.     if (wizard)
  3687.       wizard.focus();
  3688.     else {
  3689.       var variant = Cc["@mozilla.org/variant;1"].
  3690.                     createInstance(Ci.nsIWritableVariant);
  3691.       variant.setFromVariant(items);
  3692.       var features = "chrome,centerscreen,dialog,titlebar,modal";
  3693.       // This *must* be modal so as not to break startup! This code is invoked before
  3694.       // the main event loop is initiated (via checkForMismatches).
  3695.       var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  3696.                getService(Ci.nsIWindowWatcher);
  3697.       ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
  3698.     }
  3699.   },
  3700.  
  3701.   /*
  3702.    * Catch all for facilitating a version 1.0 profile upgrade.
  3703.    * 1) removes the abandoned default theme directory from the profile.
  3704.    * 2) prepares themes installed with version 1.0 for installation.
  3705.    * 3) initiates an install to populate the new extensions datasource.
  3706.    * 4) migrates the disabled attribute from the old datasource.
  3707.    * 5) migrates the app compatibility info from the old datasource.
  3708.    */
  3709.   _upgradeFromV10: function EM__upgradeFromV10() {
  3710.     var extensionsDS = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  3711.     var dsExists = extensionsDS.exists();
  3712.     // Toolkiit 1.7 profiles (Firefox 1.0, Thunderbird 1.0, etc.) have a default
  3713.     // theme directory in the profile's extensions directory that will be
  3714.     // disabled due to having a maxVersion that is incompatible with the
  3715.     // toolkit 1.8 release of the app.
  3716.     var profileDefaultTheme = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3717.                                              stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)]);
  3718.     if (profileDefaultTheme && profileDefaultTheme.exists()) {
  3719.       removeDirRecursive(profileDefaultTheme);
  3720.       // Sunbird 0.3a1 didn't move the default theme into the app's extensions
  3721.       // directory and we can't install it while uninstalling the one in the
  3722.       // profile directory. If we have a toolkit 1.8 extensions datasource and
  3723.       // a profile default theme deleting the toolkit 1.8 extensions datasource
  3724.       // will fix this problem when the datasource is re-created.
  3725.       if (dsExists)
  3726.         extensionsDS.remove(false);
  3727.     }
  3728.  
  3729.     // return early if the toolkit 1.7 extensions datasource file doesn't exist.
  3730.     var oldExtensionsFile = getFile(KEY_PROFILEDIR, [DIR_EXTENSIONS, "Extensions.rdf"]);
  3731.     if (!oldExtensionsFile.exists())
  3732.       return;
  3733.  
  3734.     // Sunbird 0.2 used a different GUID for the default theme
  3735.     profileDefaultTheme = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3736.                                          "{8af2d0a7-e394-4de2-ae55-2dae532a7a9b}"]);
  3737.     if (profileDefaultTheme && profileDefaultTheme.exists())
  3738.       removeDirRecursive(profileDefaultTheme);
  3739.  
  3740.     // Firefox 0.9 profiles may have DOMi 1.0 with just an install.rdf
  3741.     var profileDOMi = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3742.                                      "{641d8d09-7dda-4850-8228-ac0ab65e2ac9}"]);
  3743.     if (profileDOMi && profileDOMi.exists())
  3744.       removeDirRecursive(profileDOMi);
  3745.  
  3746.     // return early to avoid migrating data twice if we already have a
  3747.     // toolkit 1.8 extension datasource.
  3748.     if (dsExists)
  3749.       return;
  3750.  
  3751.     // Prepare themes for installation
  3752.     // Only enumerate directories in the app-profile and app-global locations.
  3753.     var locations = [KEY_APP_PROFILE, KEY_APP_GLOBAL, KEY_GRE_GLOBAL];
  3754.     for (var i = 0; i < locations.length; ++i) {
  3755.       var location = InstallLocations.get(locations[i]);
  3756.       if (!location.canAccess)
  3757.         continue;
  3758.  
  3759.       var entries = location.itemLocations;
  3760.       var entry;
  3761.       while ((entry = entries.nextFile)) {
  3762.         var installRDF = entry.clone();
  3763.         installRDF.append(FILE_INSTALL_MANIFEST);
  3764.  
  3765.         var chromeDir = entry.clone();
  3766.         chromeDir.append(DIR_CHROME);
  3767.  
  3768.         // It must be a directory without an install.rdf and it must contain
  3769.         // a chrome directory
  3770.         if (!entry.isDirectory() || installRDF.exists() || !chromeDir.exists())
  3771.           continue;
  3772.  
  3773.         var chromeEntries = chromeDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  3774.         if (!chromeEntries.hasMoreElements())
  3775.           continue;
  3776.  
  3777.         // We're relying on the fact that there is only one JAR file
  3778.         // in the "chrome" directory. This is a hack, but it works.
  3779.         var jarFile = chromeEntries.nextFile;
  3780.         if (jarFile.isDirectory())
  3781.           continue;
  3782.         var id = location.getIDForLocation(entry);
  3783.  
  3784.         try {
  3785.           var zipReader = getZipReaderForFile(jarFile);
  3786.           zipReader.extract(FILE_INSTALL_MANIFEST, installRDF);
  3787.  
  3788.           var contentsManifestFile = location.getItemFile(id, FILE_CONTENTS_MANIFEST);
  3789.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  3790.  
  3791.           var rootFiles = ["preview.png", "icon.png"];
  3792.           for (var i = 0; i < rootFiles.length; ++i) {
  3793.             try {
  3794.               var target = location.getItemFile(id, rootFiles[i]);
  3795.               zipReader.extract(rootFiles[i], target);
  3796.             }
  3797.             catch (e) {
  3798.             }
  3799.           }
  3800.           zipReader.close();
  3801.         }
  3802.         catch (e) {
  3803.           ERROR("ExtensionManager:_upgradeFromV10 - failed to extract theme files\r\n" +
  3804.                 "Exception: " + e);
  3805.         }
  3806.       }
  3807.     }
  3808.  
  3809.     // When upgrading from a version 1.0 profile we need to populate the
  3810.     // extensions datasource with all items before checking for incompatible
  3811.     // items since the datasource hasn't been created yet.
  3812.     var itemsToCheck = [];
  3813.     if (this._checkForFileChanges()) {
  3814.       // Create a list of all items that are to be installed so we can migrate
  3815.       // these items's settings to the new datasource.
  3816.       var items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  3817.       for (i = items.length - 1; i >= 0; --i) {
  3818.         if (items[i].locationKey == KEY_APP_PROFILE ||
  3819.             items[i].locationKey == KEY_APP_GLOBAL ||
  3820.             items[i].locationKey == KEY_GRE_GLOBAL)
  3821.           itemsToCheck.push(items[i].id);
  3822.       }
  3823.       this._finishOperations();
  3824.     }
  3825.  
  3826.     // If there are no items to migrate settings for return early.
  3827.     if (itemsToCheck.length == 0)
  3828.       return;
  3829.  
  3830.     var fileURL = getURLSpecFromFile(oldExtensionsFile);
  3831.     var oldExtensionsDS = gRDF.GetDataSourceBlocking(fileURL);
  3832.     var versionChecker = getVersionChecker();
  3833.     var ds = this.datasource;
  3834.     var currAppVersion = gApp.version;
  3835.     var currAppID = gApp.ID;
  3836.     for (var i = 0; i < itemsToCheck.length; ++i) {
  3837.       var item = ds.getItemForID(itemsToCheck[i]);
  3838.       var oldPrefix = (item.type == Ci.nsIUpdateItem.TYPE_EXTENSION) ? PREFIX_EXTENSION : PREFIX_THEME;
  3839.       var oldRes = gRDF.GetResource(oldPrefix + item.id);
  3840.       // Disable the item if it was disabled in the version 1.0 extensions
  3841.       // datasource.
  3842.       if (oldExtensionsDS.GetTarget(oldRes, EM_R("disabled"), true))
  3843.         ds.setItemProperty(item.id, EM_R("userDisabled"), EM_L("true"));
  3844.  
  3845.       // app enable all items. If it is incompatible it will be app disabled
  3846.       // later on.
  3847.       ds.setItemProperty(item.id, EM_R("appDisabled"), null);
  3848.  
  3849.       // if the item is already compatible don't attempt to migrate the
  3850.       // item's compatibility info
  3851.       var newRes = getResourceForID(itemsToCheck[i]);
  3852.       if (ds.isCompatible(ds, newRes))
  3853.         continue;
  3854.  
  3855.       var updatedMinVersion = null;
  3856.       var updatedMaxVersion = null;
  3857.       var targetApps = oldExtensionsDS.GetTargets(oldRes, EM_R("targetApplication"), true);
  3858.       while (targetApps.hasMoreElements()) {
  3859.         var targetApp = targetApps.getNext();
  3860.         if (targetApp instanceof Ci.nsIRDFResource) {
  3861.           try {
  3862.             var foundAppID = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("id"), true));
  3863.             // Different target application?  (Note:  v1.0 didn't support toolkit app ID)
  3864.             if (foundAppID != currAppID)
  3865.               continue;
  3866.  
  3867.             updatedMinVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("minVersion"), true));
  3868.             updatedMaxVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("maxVersion"), true));
  3869.  
  3870.             // Only set the target app info if the extension's target app info
  3871.             // in the version 1.0 extensions datasource makes it compatible
  3872.             if (versionChecker.compare(currAppVersion, updatedMinVersion) >= 0 &&
  3873.                 versionChecker.compare(currAppVersion, updatedMaxVersion) <= 0)
  3874.               ds.setTargetApplicationInfo(item.id, foundAppID, updatedMinVersion,
  3875.                                           updatedMaxVersion, null);
  3876.  
  3877.             break;
  3878.           }
  3879.           catch (e) {
  3880.           }
  3881.         }
  3882.       }
  3883.     }
  3884.   },
  3885.  
  3886.   /**
  3887.    * Write the Extensions List and the Startup Cache
  3888.    * @param   needsRestart
  3889.    *          true if the application needs to restart again, false otherwise.
  3890.    */
  3891.   _updateManifests: function EM__updateManifests(needsRestart) {
  3892.     // During startup we block flushing until the startup operations are all
  3893.     // complete to reduce file accesses that can trigger bug 431065
  3894.     if (gAllowFlush) {
  3895.       // Write the Startup Cache (All Items, visible or not)
  3896.       StartupCache.write();
  3897.       // Write the Extensions Locations Manifest (Visible, enabled items)
  3898.       this._updateExtensionsManifest();
  3899.     }
  3900.     else {
  3901.       gManifestNeedsFlush = true;
  3902.     }
  3903.  
  3904.     // Notify nsAppRunner to update the compatibility manifest on next startup
  3905.     this._extensionListChanged = needsRestart;
  3906.   },
  3907.  
  3908.   /**
  3909.    * Get a list of items that are currently "active" (turned on) of a specific
  3910.    * type
  3911.    * @param   type
  3912.    *          The nsIUpdateItem type to return a list of items of
  3913.    * @returns An array of active items of the specified type.
  3914.    */
  3915.   _getActiveItems: function EM__getActiveItems(type) {
  3916.     var allItems = this.getItemList(type, { });
  3917.     var activeItems = [];
  3918.     var ds = this.datasource;
  3919.     for (var i = 0; i < allItems.length; ++i) {
  3920.       var item = allItems[i];
  3921.  
  3922.       var installLocation = this.getInstallLocation(item.id);
  3923.       // An entry with an invalid install location is not active.
  3924.       if (!installLocation)
  3925.         continue;
  3926.       // An item entry is valid only if it is not disabled, not about to
  3927.       // be disabled, and not about to be uninstalled.
  3928.       if (installLocation.name in StartupCache.entries &&
  3929.           item.id in StartupCache.entries[installLocation.name] &&
  3930.           StartupCache.entries[installLocation.name][item.id]) {
  3931.         var op = StartupCache.entries[installLocation.name][item.id].op;
  3932.         if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE ||
  3933.             op == OP_NEEDS_UNINSTALL || op == OP_NEEDS_DISABLE)
  3934.           continue;
  3935.       }
  3936.       // Suppress items that have been disabled by the user or the app.
  3937.       if (ds.getItemProperty(item.id, "isDisabled") != "true")
  3938.         activeItems.push({ id: item.id, version: item.version,
  3939.                            location: installLocation });
  3940.     }
  3941.  
  3942.     return activeItems;
  3943.   },
  3944.  
  3945.   /**
  3946.    * Write the Extensions List
  3947.    */
  3948.   _updateExtensionsManifest: function EM__updateExtensionsManifest() {
  3949.     // When an operation is performed that requires a component re-registration
  3950.     // (extension enabled/disabled, installed, uninstalled), we must write the
  3951.     // set of paths where extensions live so that the startup system can determine
  3952.     // where additional components, preferences, chrome manifests etc live.
  3953.     //
  3954.     // To do this we obtain a list of active extensions and themes and write
  3955.     // these to the extensions.ini file in the profile directory.
  3956.     var validExtensions = this._getActiveItems(Ci.nsIUpdateItem.TYPE_ANY -
  3957.                                                Ci.nsIUpdateItem.TYPE_THEME);
  3958.     var validThemes     = this._getActiveItems(Ci.nsIUpdateItem.TYPE_THEME);
  3959.  
  3960.     var extensionsLocationsFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  3961.     var fos = openSafeFileOutputStream(extensionsLocationsFile);
  3962.  
  3963.     var enabledItems = [];
  3964.     var extensionSectionHeader = "[ExtensionDirs]\r\n";
  3965.     fos.write(extensionSectionHeader, extensionSectionHeader.length);
  3966.     for (var i = 0; i < validExtensions.length; ++i) {
  3967.       var e = validExtensions[i];
  3968.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(Ci.nsILocalFile);
  3969.       var descriptor = getAbsoluteDescriptor(itemLocation);
  3970.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  3971.       fos.write(line, line.length);
  3972.       enabledItems.push(e.id + ":" + e.version);
  3973.     }
  3974.  
  3975.     var themeSectionHeader = "[ThemeDirs]\r\n";
  3976.     fos.write(themeSectionHeader, themeSectionHeader.length);
  3977.     for (i = 0; i < validThemes.length; ++i) {
  3978.       var e = validThemes[i];
  3979.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(Ci.nsILocalFile);
  3980.       var descriptor = getAbsoluteDescriptor(itemLocation);
  3981.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  3982.       fos.write(line, line.length);
  3983.       enabledItems.push(e.id + ":" + e.version);
  3984.     }
  3985.  
  3986.     closeSafeFileOutputStream(fos);
  3987.  
  3988.     // Cache the enabled list for annotating the crash report subsequently
  3989.     gPref.setCharPref(PREF_EM_ENABLED_ITEMS, enabledItems.join(","));
  3990.   },
  3991.  
  3992.   /**
  3993.    * Say whether or not the Extension List has changed (and thus whether or not
  3994.    * the system will have to restart the next time it is started).
  3995.    * @param   val
  3996.    *          true if the Extension List has changed, false otherwise.
  3997.    * @returns |val|
  3998.    */
  3999.   set _extensionListChanged(val) {
  4000.     // When an extension has an operation perform on it (e.g. install, upgrade,
  4001.     // disable, etc.) we are responsible for creating the .autoreg file and
  4002.     // nsAppRunner is responsible for removing it on restart. At some point it
  4003.     // may make sense to be able to cancel a registration but for now we only
  4004.     // create the file.
  4005.     try {
  4006.       var autoregFile = getFile(KEY_PROFILEDIR, [FILE_AUTOREG]);
  4007.       if (val && !autoregFile.exists())
  4008.         autoregFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  4009.     }
  4010.     catch (e) {
  4011.     }
  4012.     return val;
  4013.   },
  4014.  
  4015.   /**
  4016.    * Gathers data about an item specified by the supplied Install Manifest
  4017.    * and determines whether or not it can be installed as-is. It makes this
  4018.    * determination by validating the item's GUID, Version, and determining
  4019.    * if it is compatible with this application.
  4020.    * @param   installManifest
  4021.    *          A nsIRDFDataSource representing the Install Manifest of the
  4022.    *          item to be installed.
  4023.    * @return  A JS Object with the following properties:
  4024.    *          "id"       The GUID of the Item being installed.
  4025.    *          "version"  The Version string of the Item being installed.
  4026.    *          "name"     The Name of the Item being installed.
  4027.    *          "type"     The nsIUpdateItem type of the Item being installed.
  4028.    *          "targetApps" An array of TargetApplication Info Objects
  4029.    *                     with "id", "minVersion" and "maxVersion" properties,
  4030.    *                     representing applications targeted by this item.
  4031.    *          "error"    The result code:
  4032.    *                     INSTALLERROR_SUCCESS
  4033.    *                       no error, item can be installed
  4034.    *                     INSTALLERROR_INVALID_GUID
  4035.    *                       error, GUID is not well-formed
  4036.    *                     INSTALLERROR_INVALID_VERSION
  4037.    *                       error, Version is not well-formed
  4038.    *                     INSTALLERROR_INCOMPATIBLE_VERSION
  4039.    *                       error, item is not compatible with this version
  4040.    *                       of the application.
  4041.    *                     INSTALLERROR_INCOMPATIBLE_PLATFORM
  4042.    *                       error, item is not compatible with the operating
  4043.    *                       system or ABI the application was built for.
  4044.    *                     INSTALLERROR_INSECURE_UPDATE
  4045.    *                       error, item has no secure method of providing updates
  4046.    *                     INSTALLERROR_BLOCKLISTED
  4047.    *                       error, item is blocklisted
  4048.    */
  4049.   _getInstallData: function EM__getInstallData(installManifest) {
  4050.     var installData = { id          : "",
  4051.                         version     : "",
  4052.                         name        : "",
  4053.                         type        : 0,
  4054.                         error       : INSTALLERROR_SUCCESS,
  4055.                         targetApps  : [],
  4056.                         updateURL   : "",
  4057.                         updateKey   : "",
  4058.                         currentApp  : null };
  4059.  
  4060.     // Fetch properties from the Install Manifest
  4061.     installData.id       = getManifestProperty(installManifest, "id");
  4062.     installData.version  = getManifestProperty(installManifest, "version");
  4063.     installData.name     = getManifestProperty(installManifest, "name");
  4064.     installData.type     = getAddonTypeFromInstallManifest(installManifest);
  4065.     installData.updateURL= getManifestProperty(installManifest, "updateURL");
  4066.     installData.updateKey= getManifestProperty(installManifest, "updateKey");
  4067.  
  4068.     /**
  4069.      * Reads a property off a Target Application resource
  4070.      * @param   resource
  4071.      *          The RDF Resource for a Target Application
  4072.      * @param   property
  4073.      *          The property (less EM_NS) to read
  4074.      * @returns The string literal value of the property.
  4075.      */
  4076.     function readTAProperty(resource, property) {
  4077.       return stringData(installManifest.GetTarget(resource, EM_R(property), true));
  4078.     }
  4079.  
  4080.     var targetApps = installManifest.GetTargets(gInstallManifestRoot,
  4081.                                                 EM_R("targetApplication"),
  4082.                                                 true);
  4083.     while (targetApps.hasMoreElements()) {
  4084.       var targetApp = targetApps.getNext();
  4085.       if (targetApp instanceof Ci.nsIRDFResource) {
  4086.         try {
  4087.           var data = { id        : readTAProperty(targetApp, "id"),
  4088.                        minVersion: readTAProperty(targetApp, "minVersion"),
  4089.                        maxVersion: readTAProperty(targetApp, "maxVersion") };
  4090.           installData.targetApps.push(data);
  4091.           if ((data.id == gApp.ID) ||
  4092.               (data.id == TOOLKIT_ID) && !installData.currentApp)
  4093.             installData.currentApp = data;
  4094.         }
  4095.         catch (e) {
  4096.           continue;
  4097.         }
  4098.       }
  4099.     }
  4100.  
  4101.     // If the item specifies one or more target platforms, make sure our OS/ABI
  4102.     // combination is in the list - otherwise, refuse to install the item.
  4103.     var targetPlatforms = null;
  4104.     try {
  4105.       targetPlatforms = installManifest.GetTargets(gInstallManifestRoot,
  4106.                                                    EM_R("targetPlatform"),
  4107.                                                    true);
  4108.     } catch(e) {
  4109.       // No targetPlatform nodes, continue.
  4110.     }
  4111.     if (targetPlatforms != null && targetPlatforms.hasMoreElements()) {
  4112.       var foundMatchingOS = false;
  4113.       var foundMatchingOSAndABI = false;
  4114.       var requireABICompatibility = false;
  4115.       while (targetPlatforms.hasMoreElements()) {
  4116.         var targetPlatform = stringData(targetPlatforms.getNext());
  4117.         var os = targetPlatform.split("_")[0];
  4118.         var index = targetPlatform.indexOf("_");
  4119.         var abi = index != -1 ? targetPlatform.substr(index + 1) : null;
  4120.         if (os == gOSTarget) {
  4121.           foundMatchingOS = true;
  4122.           // The presence of any ABI part after our OS means ABI is important.
  4123.           if (abi != null) {
  4124.             requireABICompatibility = true;
  4125.             // If we don't know our ABI, we can't be compatible
  4126.             if (abi == gXPCOMABI && abi != UNKNOWN_XPCOM_ABI) {
  4127.               foundMatchingOSAndABI = true;
  4128.               break;
  4129.             }
  4130.           }
  4131.         }
  4132.       }
  4133.       if (!foundMatchingOS || (requireABICompatibility && !foundMatchingOSAndABI)) {
  4134.         installData.error = INSTALLERROR_INCOMPATIBLE_PLATFORM;
  4135.         return installData;
  4136.       }
  4137.     }
  4138.  
  4139.     // Validate the Item ID
  4140.     if (!gIDTest.test(installData.id)) {
  4141.       installData.error = INSTALLERROR_INVALID_GUID;
  4142.       return installData;
  4143.     }
  4144.     
  4145.     // Check that the add-on provides a secure update method.
  4146.     if (gCheckUpdateSecurity &&
  4147.         installData.updateURL &&
  4148.         installData.updateURL.substring(0, 6) != "https:" &&
  4149.         !installData.updateKey) {
  4150.       installData.error = INSTALLERROR_INSECURE_UPDATE;
  4151.       return installData;
  4152.     }
  4153.       
  4154.     // Check that the target application range allows compatibility with the app
  4155.     if (gCheckCompatibility &&
  4156.         !this.datasource.isCompatible(installManifest, gInstallManifestRoot, undefined)) {
  4157.       installData.error = INSTALLERROR_INCOMPATIBLE_VERSION;
  4158.       return installData;
  4159.     }
  4160.     
  4161.     // Check if the item is blocklisted.
  4162.     if (!gBlocklist)
  4163.       gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
  4164.                    getService(Ci.nsIBlocklistService);
  4165.     var state = gBlocklist.getAddonBlocklistState(installData.id, installData.version);
  4166.     if (state == Ci.nsIBlocklistService.STATE_BLOCKED)
  4167.       installData.error = INSTALLERROR_BLOCKLISTED;
  4168.     else if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
  4169.       installData.error = INSTALLERROR_SOFTBLOCKED;
  4170.  
  4171.     return installData;
  4172.   },
  4173.  
  4174.   /**
  4175.    * Installs an item from a XPI/JAR file.
  4176.    * This is the main entry point into the Install system from outside code
  4177.    * (e.g. XPInstall).
  4178.    * @param   aXPIFile
  4179.    *          The file to install from.
  4180.    * @param   aInstallLocationKey
  4181.    *          The name of the Install Location where this item should be
  4182.    *          installed.
  4183.    */
  4184.   installItemFromFile: function EM_installItemFromFile(xpiFile, installLocationKey) {
  4185.     this.installItemFromFileInternal(xpiFile, installLocationKey, null);
  4186.  
  4187.     // If there are no compatibility checks running and no downloads in
  4188.     // progress then the install operations are complete.
  4189.     if (this._compatibilityCheckCount == 0 && this._transactions.length == 0) {
  4190.       for (var i = 0; i < this._installListeners.length; ++i)
  4191.         this._installListeners[i].onInstallsCompleted();
  4192.     }
  4193.   },
  4194.  
  4195.   /**
  4196.    * Installs an item from a XPI/JAR file.
  4197.    * @param   aXPIFile
  4198.    *          The file to install from.
  4199.    * @param   aInstallLocationKey
  4200.    *          The name of the Install Location where this item should be
  4201.    *          installed.
  4202.    * @param   aInstallManifest
  4203.    *          An updated Install Manifest from the Version Update check.
  4204.    *          Can be null when invoked from callers other than the Version
  4205.    *          Update check.
  4206.    * @returns The install result code. If this is INSTALLERROR_PHONING_HOME
  4207.    *          then a remote update check has been started to attempt to resolve
  4208.    *          compatibility problems.
  4209.    */
  4210.   installItemFromFileInternal: function EM_installItemFromFileInternal(aXPIFile,
  4211.                                                                        aInstallLocationKey,
  4212.                                                                        aInstallManifest) {
  4213.     var em = this;
  4214.     /**
  4215.      * Gets the Install Location for an Item.
  4216.      * @param   itemID
  4217.      *          The GUID of the item to find an Install Location for.
  4218.      * @return  An object implementing nsIInstallLocation which represents the
  4219.      *          location where the specified item should be installed.
  4220.      *          This can be:
  4221.      *          1. an object that corresponds to the location key supplied to
  4222.      *             |installItemFromFileInternal|,
  4223.      *          2. the default install location (the App Profile Extensions Folder)
  4224.      *             if no location key was supplied, or the location key supplied
  4225.      *             was not in the set of registered locations
  4226.      *          3. null, if the location selected by 1 or 2 above does not support
  4227.      *             installs from XPI/JAR files, or that location is not writable
  4228.      *             with the current access privileges.
  4229.      */
  4230.     function getInstallLocation(itemID) {
  4231.       // Here I use "upgrade" to mean "install a different version of an item".
  4232.       var installLocation = em.getInstallLocation(itemID);
  4233.       if (!installLocation) {
  4234.         // This is not an "upgrade", since we don't have any location data for the
  4235.         // extension ID specified - that is, it's not in our database.
  4236.  
  4237.         // Caller supplied a key to a registered location, use that location
  4238.         // for the installation
  4239.         installLocation = InstallLocations.get(aInstallLocationKey);
  4240.         if (installLocation) {
  4241.           // If the specified location does not have a common metadata location
  4242.           // (e.g. extensions have no common root, or other location specified
  4243.           // by the location implementation) - e.g. for a Registry Key enumeration
  4244.           // location - we cannot install or upgrade using a XPI file, probably
  4245.           // because these application types will be handling upgrading themselves.
  4246.           // Just bail.
  4247.           if (!installLocation.location) {
  4248.             LOG("Install Location \"" + installLocation.name + "\" does not support " +
  4249.                 "installation of items from XPI/JAR files. You must manage " +
  4250.                 "installation and update of these items yourself.");
  4251.             installLocation = null;
  4252.           }
  4253.         }
  4254.         else {
  4255.           // In the absence of a preferred install location, just default to
  4256.           // the App-Profile
  4257.           installLocation = InstallLocations.get(KEY_APP_PROFILE);
  4258.         }
  4259.       }
  4260.       else {
  4261.         // This is an "upgrade", but not through the Update System, because the
  4262.         // Update code will not let an extension with an incompatible target
  4263.         // app version range through to this point. This is an "upgrade" in the
  4264.         // sense that the user found a different version of an installed extension
  4265.         // and installed it through the web interface, so we have metadata.
  4266.  
  4267.         // If the location is different, return the preferred location rather than
  4268.         // the location of the currently installed version, because we may be in
  4269.         // the situation where an item is being installed into the global app
  4270.         // dir when there's a version in the profile dir.
  4271.         if (installLocation.name != aInstallLocationKey)
  4272.           installLocation = InstallLocations.get(aInstallLocationKey);
  4273.       }
  4274.       if (!installLocation.canAccess) {
  4275.         LOG("Install Location\"" + installLocation.name + "\" cannot be written " +
  4276.             "to with your access privileges. Installation will not proceed.");
  4277.         installLocation = null;
  4278.       }
  4279.       return installLocation;
  4280.     }
  4281.  
  4282.     /**
  4283.      * Stages a XPI file in the default item location specified by other
  4284.      * applications when they registered with XulRunner if the item's
  4285.      * install manifest specified compatibility with them.
  4286.      */
  4287.     function stageXPIForOtherApps(xpiFile, installData) {
  4288.       for (var i = 0; i < installData.targetApps.length; ++i) {
  4289.         var targetApp = installData.targetApps[i];
  4290.         if (targetApp.id != gApp.ID && targetApp.id != TOOLKIT_ID) {
  4291.         /* XXXben uncomment when this works!
  4292.           var settingsThingy = Cc[].
  4293.                                getService(Ci.nsIXULRunnerSettingsThingy);
  4294.           try {
  4295.             var appPrefix = "SOFTWARE\\Mozilla\\XULRunner\\Applications\\";
  4296.             var branch = settingsThingy.getBranch(appPrefix + targetApp.id);
  4297.             var path = branch.getProperty("ExtensionsLocation");
  4298.             var destination = Cc["@mozilla.org/file/local;1"].
  4299.                               createInstance(Ci.nsILocalFile);
  4300.             destination.initWithPath(path);
  4301.             xpiFile.copyTo(file, xpiFile.leafName);
  4302.           }
  4303.           catch (e) {
  4304.           }
  4305.          */
  4306.         }
  4307.       }
  4308.     }
  4309.  
  4310.     /**
  4311.      * Extracts and then starts the install for extensions / themes contained
  4312.      * within a xpi.
  4313.      */
  4314.     function installMultiXPI(xpiFile, installData) {
  4315.       var fileURL = getURIFromFile(xpiFile).QueryInterface(Ci.nsIURL);
  4316.       if (fileURL.fileExtension.toLowerCase() != "xpi") {
  4317.         LOG("Invalid File Extension: Item: \"" + fileURL.fileName + "\" has an " +
  4318.             "invalid file extension. Only xpi file extensions are allowed for " +
  4319.             "multiple item packages.");
  4320.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4321.         showMessage("invalidFileExtTitle", [],
  4322.                     "invalidFileExtMessage", [installData.name,
  4323.                     fileURL.fileExtension,
  4324.                     bundle.GetStringFromName("type-" + installData.type)]);
  4325.         return;
  4326.       }
  4327.  
  4328.       try {
  4329.         var zipReader = getZipReaderForFile(xpiFile);
  4330.       }
  4331.       catch (e) {
  4332.         LOG("installMultiXPI: failed to open xpi file: " + xpiFile.path);
  4333.         throw e;
  4334.       }
  4335.  
  4336.       var searchForEntries = ["*.xpi", "*.jar"];
  4337.       var files = [];
  4338.       for (var i = 0; i < searchForEntries.length; ++i) {
  4339.         var entries = zipReader.findEntries(searchForEntries[i]);
  4340.         while (entries.hasMore()) {
  4341.           var entryName = entries.getNext();
  4342.           var target = getFile(KEY_TEMPDIR, [entryName]);
  4343.           try {
  4344.             target.createUnique(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  4345.           }
  4346.           catch (e) {
  4347.             LOG("installMultiXPI: failed to create target file for extraction " +
  4348.                 " file = " + target.path + ", exception = " + e + "\n");
  4349.           }
  4350.           zipReader.extract(entryName, target);
  4351.           files.push(target);
  4352.         }
  4353.       }
  4354.       zipReader.close();
  4355.  
  4356.       if (files.length == 0) {
  4357.         LOG("Multiple Item Package: Item: \"" + fileURL.fileName + "\" does " +
  4358.             "not contain a valid package to install.");
  4359.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4360.         showMessage("missingPackageFilesTitle",
  4361.                     [bundle.GetStringFromName("type-" + installData.type)],
  4362.                     "missingPackageFilesMessage", [installData.name,
  4363.                     bundle.GetStringFromName("type-" + installData.type)]);
  4364.         return;
  4365.       }
  4366.  
  4367.       for (i = 0; i < files.length; ++i) {
  4368.         em.installItemFromFileInternal(files[i], aInstallLocationKey, null);
  4369.         files[i].remove(false);
  4370.       }
  4371.     }
  4372.  
  4373.     /**
  4374.      * An observer for the Extension Update System.
  4375.      * @constructor
  4376.      */
  4377.     function IncompatibleObserver() {}
  4378.     IncompatibleObserver.prototype = {
  4379.       _xpi: null,
  4380.       _installManifest: null,
  4381.  
  4382.       /**
  4383.        * Ask the Extension Update System if there are any version updates for
  4384.        * this item that will allow it to be compatible with this version of
  4385.        * the Application.
  4386.        * @param   item
  4387.        *          An nsIUpdateItem representing the item being installed.
  4388.        * @param   installManifest
  4389.        *          The Install Manifest datasource for the item.
  4390.        * @param   xpiFile
  4391.        *          The staged source XPI file that contains the item. Cleaned
  4392.        *          up by this process.
  4393.        * @param   installRDF
  4394.        *          The install.rdf file that was extracted from the xpi.
  4395.        */
  4396.       checkForUpdates: function IncObs_checkForUpdates(item, installManifest, xpiFile) {
  4397.         this._xpi             = xpiFile;
  4398.         this._installManifest = installManifest;
  4399.  
  4400.         for (var i = 0; i < em._installListeners.length; ++i)
  4401.           em._installListeners[i].onCompatibilityCheckStarted(item);
  4402.         em._compatibilityCheckCount++;
  4403.         em.update([item], 1, Ci.nsIExtensionManager.UPDATE_CHECK_COMPATIBILITY, this);
  4404.       },
  4405.  
  4406.       /**
  4407.        * See nsIExtensionManager.idl
  4408.        */
  4409.       onUpdateStarted: function IncObs_onUpdateStarted() {
  4410.         LOG("Phone Home Listener: Update Started");
  4411.       },
  4412.  
  4413.       /**
  4414.        * See nsIExtensionManager.idl
  4415.        */
  4416.       onUpdateEnded: function IncObs_onUpdateEnded() {
  4417.         LOG("Phone Home Listener: Update Ended");
  4418.       },
  4419.  
  4420.       /**
  4421.        * See nsIExtensionManager.idl
  4422.        */
  4423.       onAddonUpdateStarted: function IncObs_onAddonUpdateStarted(addon) {
  4424.         if (!addon)
  4425.           throw Cr.NS_ERROR_INVALID_ARG;
  4426.  
  4427.         LOG("Phone Home Listener: Update For " + addon.id + " started");
  4428.         em.datasource.addIncompatibleUpdateItem(addon.name, this._xpi.path,
  4429.                                                 addon.type, addon.version);
  4430.       },
  4431.  
  4432.       /**
  4433.        * See nsIExtensionManager.idl
  4434.        */
  4435.       onAddonUpdateEnded: function IncObs_onAddonUpdateEnded(addon, status) {
  4436.         if (!addon)
  4437.           throw Cr.NS_ERROR_INVALID_ARG;
  4438.  
  4439.         LOG("Phone Home Listener: Update For " + addon.id + " ended, status = " + status);
  4440.         em.datasource.removeDownload(this._xpi.path);
  4441.         LOG("Version Check Phone Home Completed");
  4442.  
  4443.         for (var i = 0; i < em._installListeners.length; ++i)
  4444.           em._installListeners[i].onCompatibilityCheckEnded(addon, status);
  4445.  
  4446.         // Only compatibility updates (e.g. STATUS_VERSIONINFO) are currently
  4447.         // supported
  4448.         if (status == Ci.nsIAddonUpdateCheckListener.STATUS_VERSIONINFO) {
  4449.           em.datasource.setTargetApplicationInfo(addon.id,
  4450.                                                  addon.targetAppID,
  4451.                                                  addon.minAppVersion,
  4452.                                                  addon.maxAppVersion,
  4453.                                                  this._installManifest);
  4454.  
  4455.           // Try and install again, but use the updated compatibility DB.
  4456.           // This will send out an apropriate onInstallEnded notification for us.
  4457.           var status = em.installItemFromFileInternal(this._xpi,
  4458.                                                       aInstallLocationKey,
  4459.                                                       this._installManifest);
  4460.  
  4461.           // The install may still have failed at this point due to the blocklist
  4462.           if (status == INSTALLERROR_SUCCESS) {
  4463.             // Add the updated compatibility info to the datasource if done
  4464.             if (StartupCache.entries[aInstallLocationKey][addon.id].op == OP_NONE) {
  4465.               em.datasource.setTargetApplicationInfo(addon.id,
  4466.                                                      addon.targetAppID,
  4467.                                                      addon.minAppVersion,
  4468.                                                      addon.maxAppVersion,
  4469.                                                      null);
  4470.             }
  4471.             else { // needs a restart
  4472.               // Add updatedMinVersion and updatedMaxVersion so it can be used
  4473.               // to update the datasource during the installation or upgrade.
  4474.               em.datasource.setUpdatedTargetAppInfo(addon.id,
  4475.                                                     addon.targetAppID,
  4476.                                                     addon.minAppVersion,
  4477.                                                     addon.maxAppVersion,
  4478.                                                     null);
  4479.             }
  4480.           }
  4481.         }
  4482.         else {
  4483.           em.datasource.removeDownload(this._xpi.path);
  4484.           showIncompatibleError(installData);
  4485.           LOG("Add-on " + addon.id + " is incompatible with " +
  4486.               BundleManager.appName + " " + gApp.version + ", Toolkit " +
  4487.               gApp.platformVersion + ". Remote compatibility check did not " +
  4488.               "resolve this.");
  4489.           
  4490.           for (var i = 0; i < em._installListeners.length; ++i)
  4491.             em._installListeners[i].onInstallEnded(addon, INSTALLERROR_INCOMPATIBLE_VERSION);
  4492.  
  4493.           // We are responsible for cleaning up this file!
  4494.           InstallLocations.get(aInstallLocationKey).removeFile(this._xpi);
  4495.         }
  4496.  
  4497.         em._compatibilityCheckCount--;
  4498.         // If there are no more compatibility checks running and no downloads in
  4499.         // progress then the install operations are complete.
  4500.         if (em._compatibilityCheckCount == 0 && em._transactions.length == 0) {
  4501.           for (var i = 0; i < em._installListeners.length; ++i)
  4502.             em._installListeners[i].onInstallsCompleted();
  4503.         }
  4504.       },
  4505.  
  4506.       QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonUpdateCheckListener])
  4507.     }
  4508.  
  4509.     var shouldPhoneHomeIfNecessary = false;
  4510.     if (!aInstallManifest) {
  4511.       // If we were not called with an Install Manifest, we were called from
  4512.       // some other path than the Phone Home system, so we do want to phone
  4513.       // home if the version is incompatible. As this is the first point in the
  4514.       // install process we must notify observers here.
  4515.  
  4516.       var addon = makeItem(getURIFromFile(aXPIFile).spec, "",
  4517.                            aInstallLocationKey, "", "", "",
  4518.                            getURIFromFile(aXPIFile).spec,
  4519.                            "", "", "", "", 0, gApp.id);
  4520.       for (var i = 0; i < this._installListeners.length; ++i)
  4521.         this._installListeners[i].onInstallStarted(addon);
  4522.  
  4523.       shouldPhoneHomeIfNecessary = true;
  4524.       var installManifest = null;
  4525.       var installManifestFile = extractRDFFileToTempDir(aXPIFile,
  4526.                                                         FILE_INSTALL_MANIFEST,
  4527.                                                         true);
  4528.       if (installManifestFile.exists()) {
  4529.         installManifest = getInstallManifest(installManifestFile);
  4530.         installManifestFile.remove(false);
  4531.       }
  4532.       if (!installManifest) {
  4533.         LOG("The Install Manifest supplied by this item is not well-formed. " +
  4534.             "Installation will not proceed.");
  4535.         for (var i = 0; i < this._installListeners.length; ++i)
  4536.           this._installListeners[i].onInstallEnded(addon, INSTALLERROR_INVALID_MANIFEST);
  4537.         return INSTALLERROR_INVALID_MANIFEST;
  4538.       }
  4539.     }
  4540.     else
  4541.       installManifest = aInstallManifest;
  4542.  
  4543.     var installData = this._getInstallData(installManifest);
  4544.     // Recreate the add-on item with the full detail from the install manifest
  4545.     addon = makeItem(installData.id, installData.version,
  4546.                      aInstallLocationKey,
  4547.                      installData.currentApp ? installData.currentApp.minVersion : "",
  4548.                      installData.currentApp ? installData.currentApp.maxVersion : "",
  4549.                      installData.name,
  4550.                      getURIFromFile(aXPIFile).spec,
  4551.                      "", /* XPI Update Hash */
  4552.                      "", /* Icon URL */
  4553.                      installData.updateURL || "",
  4554.                      installData.updateKey || "",
  4555.                      installData.type,
  4556.                      installData.currentApp ? installData.currentApp.id : "");
  4557.  
  4558.     switch (installData.error) {
  4559.     case INSTALLERROR_INCOMPATIBLE_VERSION:
  4560.       // Since the caller cleans up |aXPIFile|, and we're not yet sure whether or
  4561.       // not we need it (we may need it if a remote version bump that makes it
  4562.       // compatible is discovered by the call home) - so we must stage it for
  4563.       // later ourselves.
  4564.       if (shouldPhoneHomeIfNecessary && installData.currentApp) {
  4565.         var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  4566.         if (!installLocation)
  4567.           return INSTALLERROR_INCOMPATIBLE_VERSION;
  4568.         var stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  4569.         (new IncompatibleObserver(this)).checkForUpdates(addon, installManifest,
  4570.                                                          stagedFile);
  4571.         // Return early to prevent deletion of the install manifest file.
  4572.         return INSTALLERROR_PHONING_HOME;
  4573.       }
  4574.       else {
  4575.         // XXXben Look up XULRunnerSettingsThingy to see if there is a registered
  4576.         //        app that can handle this item, if so just stage and don't show
  4577.         //        this error!
  4578.         showIncompatibleError(installData);
  4579.         LOG("Add-on " + installData.id + " is incompatible with " +
  4580.             BundleManager.appName + " " + gApp.version + ", Toolkit " +
  4581.             gApp.platformVersion + ". Remote compatibility check was not performed.");
  4582.       }
  4583.       break;
  4584.     case INSTALLERROR_SOFTBLOCKED:
  4585.       if (!showBlocklistMessage(installData, true))
  4586.         break;
  4587.       installData.error = INSTALLERROR_SUCCESS;
  4588.       // Fall through to continue the install
  4589.     case INSTALLERROR_SUCCESS:
  4590.       // Installation of multiple extensions / themes contained within a single xpi.
  4591.       if (installData.type == Ci.nsIUpdateItem.TYPE_MULTI_XPI) {
  4592.         installMultiXPI(aXPIFile, installData);
  4593.         break;
  4594.       }
  4595.  
  4596.       // Stage the extension's XPI so it can be extracted at the next restart.
  4597.       var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  4598.       if (!installLocation) {
  4599.         // No cleanup of any of the staged XPI files should be required here,
  4600.         // because this should only ever fail on the first recurse through
  4601.         // this function, BEFORE staging takes place... technically speaking
  4602.         // a location could become readonly during the phone home process,
  4603.         // but that's an edge case I don't care about.
  4604.         for (var i = 0; i < this._installListeners.length; ++i)
  4605.           this._installListeners[i].onInstallEnded(addon, INSTALLERROR_RESTRICTED);
  4606.         return INSTALLERROR_RESTRICTED;
  4607.       }
  4608.  
  4609.       // Stage a copy of the XPI/JAR file for our own evil purposes...
  4610.       stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  4611.  
  4612.       var restartRequired = this.installRequiresRestart(installData.id,
  4613.                                                         installData.type);
  4614.       // Determine which configuration function to use based on whether or not
  4615.       // there is data about this item in our datasource already - if there is
  4616.       // we want to upgrade, otherwise we install fresh.
  4617.       var ds = this.datasource;
  4618.       if (installData.id in ds.visibleItems && ds.visibleItems[installData.id]) {
  4619.         // We enter this function if any data corresponding to an existing GUID
  4620.         // is found, regardless of its Install Location. We need to check before
  4621.         // "upgrading" an item that Install Location of the new item is of equal
  4622.         // or higher priority than the old item, to make sure the datasource only
  4623.         // ever tracks metadata for active items.
  4624.         var oldInstallLocation = this.getInstallLocation(installData.id);
  4625.         if (oldInstallLocation.priority >= installLocation.priority) {
  4626.           this._upgradeItem(installManifest, installData.id, installLocation,
  4627.                             installData.type);
  4628.           if (!restartRequired) {
  4629.             this._finalizeUpgrade(installData.id, installLocation);
  4630.             this._finalizeInstall(installData.id, stagedFile);
  4631.           }
  4632.         }
  4633.       }
  4634.       else {
  4635.         this._configureForthcomingItem(installManifest, installData.id,
  4636.                                         installLocation, installData.type);
  4637.         if (!restartRequired) {
  4638.           this._finalizeInstall(installData.id, stagedFile);
  4639.           if (installData.type == Ci.nsIUpdateItem.TYPE_THEME) {
  4640.             var internalName = this.datasource.getItemProperty(installData.id, "internalName");
  4641.             if (gPref.getBoolPref(PREF_EM_DSS_ENABLED)) {
  4642.               gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, internalName);
  4643.             }
  4644.             else {
  4645.               gPref.setBoolPref(PREF_DSS_SWITCHPENDING, true);
  4646.               gPref.setCharPref(PREF_DSS_SKIN_TO_SELECT, internalName);
  4647.             }
  4648.           }
  4649.         }
  4650.       }
  4651.       this._updateManifests(restartRequired);
  4652.       break;
  4653.     case INSTALLERROR_INVALID_GUID:
  4654.       LOG("Invalid GUID: Item has GUID: \"" + installData.id + "\"" +
  4655.           " which is not well-formed.");
  4656.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4657.       showMessage("incompatibleTitle",
  4658.                   [bundle.GetStringFromName("type-" + installData.type)],
  4659.                   "invalidGUIDMessage", [installData.name, installData.id]);
  4660.       break;
  4661.     case INSTALLERROR_INVALID_VERSION:
  4662.       LOG("Invalid Version: Item: \"" + installData.id + "\" has version " +
  4663.           installData.version + " which is not well-formed.");
  4664.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4665.       showMessage("incompatibleTitle",
  4666.                   [bundle.GetStringFromName("type-" + installData.type)],
  4667.                   "invalidVersionMessage", [installData.name, installData.version]);
  4668.       break;
  4669.     case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  4670.       const osABI = gOSTarget + "_" + gXPCOMABI;
  4671.       LOG("Incompatible Platform: Item: \"" + installData.id + "\" is not " +
  4672.           "compatible with '" + osABI + "'.");
  4673.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4674.       showMessage("incompatibleTitle",
  4675.                   [bundle.GetStringFromName("type-" + installData.type)],
  4676.                   "incompatiblePlatformMessage",
  4677.                   [installData.name, BundleManager.appName, osABI]);
  4678.       break;
  4679.     case INSTALLERROR_BLOCKLISTED:
  4680.       LOG("Blocklisted Item: Item: \"" + installData.id + "\" version " +
  4681.           installData.version + " was not installed.");
  4682.       showBlocklistMessage(installData, false);
  4683.       break;
  4684.     case INSTALLERROR_INSECURE_UPDATE:
  4685.       LOG("No secure updates: Item: \"" + installData.id + "\" version " + 
  4686.           installData.version + " was not installed.");
  4687.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4688.       showMessage("incompatibleTitle", 
  4689.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4690.                   "insecureUpdateMessage", [installData.name]);
  4691.       break;
  4692.     default:
  4693.       break;
  4694.     }
  4695.  
  4696.     // Check to see if this item supports other applications and in that case
  4697.     // stage the the XPI file in the location specified by those applications.
  4698.     stageXPIForOtherApps(aXPIFile, installData);
  4699.  
  4700.     // The install of this item is complete, notify observers
  4701.     for (var i = 0; i < this._installListeners.length; ++i)
  4702.       this._installListeners[i].onInstallEnded(addon, installData.error);
  4703.  
  4704.     return installData.error;
  4705.   },
  4706.  
  4707.   /**
  4708.    * Whether or not this type's installation/uninstallation requires
  4709.    * the application to be restarted.
  4710.    * @param   id
  4711.    *          The GUID of the item
  4712.    * @param   type
  4713.    *          The nsIUpdateItem type of the item
  4714.    * @returns true if installation of an item of this type requires a
  4715.    *          restart.
  4716.    */
  4717.   installRequiresRestart: function EM_installRequiresRestart(id, type) {
  4718.     switch (type) {
  4719.     case Ci.nsIUpdateItem.TYPE_THEME:
  4720.       var internalName = this.datasource.getItemProperty(id, "internalName");
  4721.       var needsRestart = false;
  4722.       if (gPref.prefHasUserValue(PREF_DSS_SKIN_TO_SELECT))
  4723.         needsRestart = internalName == gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  4724.       if (!needsRestart &&
  4725.           gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN))
  4726.         needsRestart = internalName == gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
  4727.       return needsRestart;
  4728.     }
  4729.     return ((type & Ci.nsIUpdateItem.TYPE_ADDON) > 0);
  4730.   },
  4731.  
  4732.   /**
  4733.    * Perform initial configuration on an item that has just or will be
  4734.    * installed. This inserts the item into the appropriate container in the
  4735.    * datasource, so that the application UI shows the item even if it will
  4736.    * not actually be installed until the next restart.
  4737.    * @param   installManifest
  4738.    *          The Install Manifest datasource that describes this item.
  4739.    * @param   id
  4740.    *          The GUID of this item.
  4741.    * @param   installLocation
  4742.    *          The Install Location where this item is installed.
  4743.    * @param   type
  4744.    *          The nsIUpdateItem type of this item.
  4745.    */
  4746.   _configureForthcomingItem: function EM__configureForthcomingItem(installManifest,
  4747.                                                                    id,
  4748.                                                                    installLocation,
  4749.                                                                    type) {
  4750.     var ds = this.datasource;
  4751.     ds.updateVisibleList(id, installLocation.name, false);
  4752.  
  4753.     var name = null;
  4754.     var localized = findClosestLocalizedResource(installManifest, gInstallManifestRoot);
  4755.     if (localized)
  4756.       name = installManifest.GetTarget(localized, EM_R("name"), true);
  4757.     else
  4758.       name = EM_L(getManifestProperty(installManifest, "name"));
  4759.  
  4760.     var props = { name            : name,
  4761.                   version         : EM_L(getManifestProperty(installManifest, "version")),
  4762.                   newVersion      : EM_L(getManifestProperty(installManifest, "version")),
  4763.                   installLocation : EM_L(installLocation.name),
  4764.                   type            : EM_I(type),
  4765.                   availableUpdateURL    : null,
  4766.                   availableUpdateHash   : null,
  4767.                   availableUpdateVersion: null,
  4768.                   availableUpdateInfo   : null };
  4769.     ds.setItemProperties(id, props);
  4770.     ds.updateProperty(id, "availableUpdateURL");
  4771.  
  4772.     this._setOp(id, OP_NEEDS_INSTALL);
  4773.  
  4774.     // Insert it into the child list NOW rather than later because:
  4775.     // - extensions installed using the command line need to be a member
  4776.     //   of a container during the install phase for the code to be able
  4777.     //   to identify profile vs. global
  4778.     // - extensions installed through the UI should show some kind of
  4779.     //   feedback to indicate their presence is forthcoming (i.e. they
  4780.     //   will be available after a restart).
  4781.     ds.insertItemIntoContainer(id);
  4782.  
  4783.     this._notifyAction(id, EM_ITEM_INSTALLED);
  4784.   },
  4785.  
  4786.   /**
  4787.    * Perform configuration on an item that has just or will be upgraded.
  4788.    * @param   installManifest
  4789.    *          The Install Manifest datasource that describes this item.
  4790.    * @param   itemID
  4791.    *          The GUID of this item.
  4792.    * @param   installLocation
  4793.    *          The Install Location where this item is installed.
  4794.    * @param   type
  4795.    *          The nsIUpdateItem type of this item.
  4796.    */
  4797.   _upgradeItem: function EM__upgradeItem(installManifest, id, installLocation, type) {
  4798.     // Don't change any props that would need to be reset if the install fails.
  4799.     // They will be reset as appropriate by the upgrade/install process.
  4800.     var ds = this.datasource;
  4801.     ds.updateVisibleList(id, installLocation.name, false);
  4802.     var props = { installLocation : EM_L(installLocation.name),
  4803.                   type            : EM_I(type),
  4804.                   newVersion      : EM_L(getManifestProperty(installManifest, "version")),
  4805.                   availableUpdateURL      : null,
  4806.                   availableUpdateHash     : null,
  4807.                   availableUpdateVersion  : null,
  4808.                   availableUpdateInfo     : null };
  4809.     ds.setItemProperties(id, props);
  4810.     ds.updateProperty(id, "availableUpdateURL");
  4811.  
  4812.     this._setOp(id, OP_NEEDS_UPGRADE);
  4813.     this._notifyAction(id, EM_ITEM_UPGRADED);
  4814.   },
  4815.  
  4816.   /**
  4817.    * Completes an Extension's installation.
  4818.    * @param   id
  4819.    *          The GUID of the Extension to install.
  4820.    * @param   file
  4821.    *          The XPI/JAR file to install from. If this is null, we try to
  4822.    *          determine the stage file location from the ID.
  4823.    */
  4824.   _finalizeInstall: function EM__finalizeInstall(id, file) {
  4825.     var ds = this.datasource;
  4826.     var type = ds.getItemProperty(id, "type");
  4827.     if (id == 0 || id == -1) {
  4828.       ds.removeCorruptItem(id);
  4829.       return;
  4830.     }
  4831.     var installLocation = this.getInstallLocation(id);
  4832.     if (!installLocation) {
  4833.       // If the install location is null, that means we've reached the finalize
  4834.       // state without the item ever having metadata added for it, which implies
  4835.       // bogus data in the Startup Cache. Clear the entries and don't do anything
  4836.       // else.
  4837.       var entries = StartupCache.findEntries(id);
  4838.       for (var i = 0; i < entries.length; ++i) {
  4839.         var location = InstallLocations.get(entries[i].location);
  4840.         StartupCache.clearEntry(location, id);
  4841.         PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4842.       }
  4843.       return;
  4844.     }
  4845.     var itemLocation = installLocation.getItemLocation(id);
  4846.  
  4847.     if (!file && "stageFile" in installLocation)
  4848.       file = installLocation.getStageFile(id);
  4849.  
  4850.     // If |file| is null or does not exist, the installer assumes the item is
  4851.     // a dropped-in directory.
  4852.     var installer = new Installer(this.datasource, id, installLocation, type);
  4853.     installer.installFromFile(file);
  4854.  
  4855.     // If the file was staged, we must clean it up ourselves, otherwise the
  4856.     // EM caller is responsible for doing so (e.g. XPInstall)
  4857.     if (file)
  4858.       installLocation.removeFile(file);
  4859.  
  4860.     // Clear the op flag from the Startup Cache and Pending Operations sets
  4861.     StartupCache.put(installLocation, id, OP_NONE, true);
  4862.     PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4863.   },
  4864.  
  4865.   /**
  4866.    * Removes an item's metadata in preparation for an upgrade-install.
  4867.    * @param   id
  4868.    *          The GUID of the item to uninstall.
  4869.    * @param   installLocation
  4870.    *          The nsIInstallLocation of the item
  4871.    */
  4872.   _finalizeUpgrade: function EM__finalizeUpgrade(id, installLocation) {
  4873.     // Retrieve the item properties *BEFORE* we clean the resource!
  4874.     var ds = this.datasource;
  4875.  
  4876.     var stagedFile = null;
  4877.     if ("getStageFile" in installLocation)
  4878.       stagedFile = installLocation.getStageFile(id);
  4879.  
  4880.     if (stagedFile)
  4881.       var installRDF = extractRDFFileToTempDir(stagedFile, FILE_INSTALL_MANIFEST, true);
  4882.     else
  4883.       installRDF = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST);
  4884.     if (installRDF.exists()) {
  4885.       var installManifest = getInstallManifest(installRDF);
  4886.       if (installManifest) {
  4887.         var type = getAddonTypeFromInstallManifest(installManifest);
  4888.         var userDisabled = ds.getItemProperty(id, "userDisabled") == "true";
  4889.  
  4890.         // Clean the item resource
  4891.         ds.removeItemMetadata(id);
  4892.         // Now set up the properties on the item to mimic an item in its
  4893.         // "initial state" for installation.
  4894.         this._configureForthcomingItem(installManifest, id, installLocation,
  4895.                                        type);
  4896.         if (userDisabled)
  4897.           ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  4898.       }
  4899.       if (stagedFile)
  4900.         installRDF.remove(false);
  4901.     }
  4902.     // Clear the op flag from the Pending Operations set. Do NOT clear op flag in
  4903.     // the startup cache since this may have been reset to OP_NEEDS_INSTALL by
  4904.     // |_configureForthcomingItem|.
  4905.     PendingOperations.clearItem(OP_NEEDS_UPGRADE, id);
  4906.   },
  4907.  
  4908.   /**
  4909.    * Completes an item's uninstallation.
  4910.    * @param   id
  4911.    *          The GUID of the item to uninstall.
  4912.    */
  4913.   _finalizeUninstall: function EM__finalizeUninstall(id) {
  4914.     var ds = this.datasource;
  4915.  
  4916.     var installLocation = this.getInstallLocation(id);
  4917.     if (!installLocation.itemIsManagedIndependently(id)) {
  4918.       try {
  4919.         // Having a callback that does nothing just causes the directory to be
  4920.         // removed.
  4921.         safeInstallOperation(id, installLocation,
  4922.                              { data: null, callback: function() { } });
  4923.       }
  4924.       catch (e) {
  4925.         ERROR("_finalizeUninstall: failed to remove directory for item: " + id +
  4926.               " at Install Location: " + installLocation.name + ", rolling back uninstall");
  4927.         var manifest = installLocation.getItemFile(id, "FILE_INSTALL_MANIFEST");
  4928.         // If there is no manifest then either the rollback failed, or there was
  4929.         // no manifest in the first place. Either way this item is now invalid
  4930.         // and we shouldn't try to re-install it.
  4931.         if (manifest.exists()) {
  4932.           // Removal of the files failed, reset the uninstalled flag and rewrite
  4933.           // the install manifests so this item's components are registered.
  4934.           // Clear the op flag from the Startup Cache
  4935.           StartupCache.put(installLocation, id, OP_NONE, true);
  4936.           var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  4937.           this._updateManifests(restartRequired);
  4938.           return;
  4939.         }
  4940.       }
  4941.     }
  4942.     else if (installLocation.name == KEY_APP_PROFILE ||
  4943.              installLocation.name == KEY_GRE_GLOBAL ||
  4944.              installLocation.name == KEY_APP_GLOBAL ||
  4945.              installLocation.name == KEY_APP_SYSTEM_USER) {
  4946.       // Check for a pointer file and remove it if it exists
  4947.       var pointerFile = installLocation.location.clone();
  4948.       pointerFile.append(id);
  4949.       if (pointerFile.exists() && !pointerFile.isDirectory())
  4950.         pointerFile.remove(false);
  4951.     }
  4952.  
  4953.     // Clean the item resource
  4954.     ds.removeItemMetadata(id);
  4955.  
  4956.     // Do this LAST since inferences are made about an item based on
  4957.     // what container it's in.
  4958.     ds.removeItemFromContainer(id);
  4959.  
  4960.     // Clear the op flag from the Startup Cache and the Pending Operations set.
  4961.     StartupCache.clearEntry(installLocation, id);
  4962.     PendingOperations.clearItem(OP_NEEDS_UNINSTALL, id);
  4963.   },
  4964.  
  4965.   /**
  4966.    * Uninstalls an item. If the uninstallation cannot be performed immediately
  4967.    * it is scheduled for the next restart.
  4968.    * @param   id
  4969.    *          The GUID of the item to uninstall.
  4970.    */
  4971.   uninstallItem: function EM_uninstallItem(id) {
  4972.     var ds = this.datasource;
  4973.     ds.updateDownloadState(PREFIX_ITEM_URI + id, null);
  4974.     if (!ds.isDownloadItem(id)) {
  4975.       var opType = ds.getItemProperty(id, "opType");
  4976.       var installLocation = this.getInstallLocation(id);
  4977.       // Removes any staged xpis for this item.
  4978.       if (opType == OP_NEEDS_UPGRADE || opType == OP_NEEDS_INSTALL) {
  4979.         var stageFile = installLocation.getStageFile(id);
  4980.         if (stageFile)
  4981.           installLocation.removeFile(stageFile);
  4982.       }
  4983.       // Addons with an opType of OP_NEEDS_INSTALL only have a staged xpi file
  4984.       // and are removed immediately since the uninstall can't be canceled.
  4985.       if (opType == OP_NEEDS_INSTALL) {
  4986.         ds.removeItemMetadata(id);
  4987.         ds.removeItemFromContainer(id);
  4988.         ds.updateVisibleList(id, null, true);
  4989.         StartupCache.clearEntry(installLocation, id);
  4990.         this._updateManifests(false);
  4991.       }
  4992.       else {
  4993.         if (opType == OP_NEEDS_UPGRADE)
  4994.           ds.setItemProperty(id, EM_R("newVersion"), null);
  4995.         this._setOp(id, OP_NEEDS_UNINSTALL);
  4996.         var type = ds.getItemProperty(id, "type");
  4997.         var restartRequired = this.installRequiresRestart(id, type);
  4998.         if (!restartRequired) {
  4999.           this._finalizeUninstall(id);
  5000.           this._updateManifests(restartRequired);
  5001.         }
  5002.       }
  5003.     }
  5004.     else {
  5005.       // Bad download entry - uri is url, e.g. "http://www.foo.com/test.xpi"
  5006.       // ... just remove it from the list.
  5007.       ds.removeCorruptDLItem(id);
  5008.     }
  5009.  
  5010.     this._notifyAction(id, EM_ITEM_UNINSTALLED);
  5011.   },
  5012.  
  5013.   /* See nsIExtensionManager.idl */
  5014.   cancelInstallItem: function EM_cancelInstallItem(id) {
  5015.     var ds = this.datasource;
  5016.     var opType = ds.getItemProperty(id, "opType");
  5017.     if (opType != OP_NEEDS_UPGRADE && opType != OP_NEEDS_INSTALL)
  5018.       return;
  5019.  
  5020.     ds.updateDownloadState(PREFIX_ITEM_URI + id, null);
  5021.     var installLocation = this.getInstallLocation(id);
  5022.     // Removes any staged xpis for this item.
  5023.     var stageFile = installLocation.getStageFile(id);
  5024.     if (stageFile)
  5025.       installLocation.removeFile(stageFile);
  5026.     // Addons with an opType of OP_NEEDS_INSTALL only have a staged xpi file
  5027.     // and just need to be removed completely from the ds.
  5028.     if (opType == OP_NEEDS_INSTALL) {
  5029.       ds.removeItemMetadata(id);
  5030.       ds.removeItemFromContainer(id);
  5031.       ds.updateVisibleList(id, null, true);
  5032.       StartupCache.clearEntry(installLocation, id);
  5033.       this._updateManifests(false);
  5034.       this._notifyAction(id, EM_ITEM_CANCEL);
  5035.     }
  5036.     else {
  5037.       // Clear upgrade information and reset any request to enable/disable.
  5038.       ds.setItemProperty(id, EM_R("newVersion"), null);
  5039.       var appDisabled = ds.getItemProperty(id, "appDisabled");
  5040.       var userDisabled = ds.getItemProperty(id, "userDisabled");
  5041.       if (appDisabled == "true" || appDisabled == OP_NONE && userDisabled == OP_NONE) {
  5042.         this._setOp(id, OP_NONE);
  5043.         this._notifyAction(id, EM_ITEM_CANCEL);
  5044.       }
  5045.       else if (appDisabled == OP_NEEDS_DISABLE || userDisabled == OP_NEEDS_DISABLE) {
  5046.         this._setOp(id, OP_NEEDS_DISABLE);
  5047.         this._notifyAction(id, EM_ITEM_DISABLED);
  5048.       }
  5049.       else if (appDisabled == OP_NEEDS_ENABLE || userDisabled == OP_NEEDS_ENABLE) {
  5050.         this._setOp(id, OP_NEEDS_ENABLE);
  5051.         this._notifyAction(id, EM_ITEM_ENABLED);
  5052.       }
  5053.       else {
  5054.         this._setOp(id, OP_NONE);
  5055.         this._notifyAction(id, EM_ITEM_CANCEL);
  5056.       }
  5057.     }
  5058.   },
  5059.  
  5060.   /**
  5061.    * Cancels a pending uninstall of an item
  5062.    * @param   id
  5063.    *          The ID of the item.
  5064.    */
  5065.   cancelUninstallItem: function EM_cancelUninstallItem(id) {
  5066.     var ds = this.datasource;
  5067.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5068.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5069.     if (appDisabled == "true" || appDisabled == OP_NONE && userDisabled == OP_NONE) {
  5070.       this._setOp(id, OP_NONE);
  5071.       this._notifyAction(id, EM_ITEM_CANCEL);
  5072.     }
  5073.     else if (appDisabled == OP_NEEDS_DISABLE || userDisabled == OP_NEEDS_DISABLE) {
  5074.       this._setOp(id, OP_NEEDS_DISABLE);
  5075.       this._notifyAction(id, EM_ITEM_DISABLED);
  5076.     }
  5077.     else if (appDisabled == OP_NEEDS_ENABLE || userDisabled == OP_NEEDS_ENABLE) {
  5078.       this._setOp(id, OP_NEEDS_ENABLE);
  5079.       this._notifyAction(id, EM_ITEM_ENABLED);
  5080.     }
  5081.     else {
  5082.       this._setOp(id, OP_NONE);
  5083.       this._notifyAction(id, EM_ITEM_CANCEL);
  5084.     }
  5085.   },
  5086.  
  5087.   /**
  5088.    * Sets the pending operation for a visible item.
  5089.    * @param   id
  5090.    *          The GUID of the item
  5091.    * @param   op
  5092.    *          The name of the operation to be performed
  5093.    */
  5094.   _setOp: function EM__setOp(id, op) {
  5095.     var location = this.getInstallLocation(id);
  5096.     StartupCache.put(location, id, op, true);
  5097.     PendingOperations.addItem(op, { locationKey: location.name, id: id });
  5098.     var ds = this.datasource;
  5099.     if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE)
  5100.       ds.updateDownloadState(PREFIX_ITEM_URI + id, "success");
  5101.  
  5102.     ds.updateProperty(id, "opType");
  5103.     ds.updateProperty(id, "updateable");
  5104.     ds.updateProperty(id, "satisfiesDependencies");
  5105.     var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  5106.     this._updateDependentItemsForID(id);
  5107.     this._updateManifests(restartRequired);
  5108.   },
  5109.  
  5110.   /**
  5111.    * Note on appDisabled and userDisabled property arcs.
  5112.    * The appDisabled and userDisabled RDF property arcs are used to store
  5113.    * the pending operation for app disabling and user disabling for an item as
  5114.    * well as the user and app disabled status after the pending operation has
  5115.    * been completed upon restart. When the appDisabled value changes the value
  5116.    * of userDisabled is reset to prevent the state of widgets and status
  5117.    * messages from being in an incorrect state.
  5118.    */
  5119.  
  5120.   /**
  5121.    * Enables an item for the application (e.g. the item satisfies all
  5122.    * requirements like app compatibility for it to be enabled). The appDisabled
  5123.    * property arc will be removed if the item will be app disabled on next
  5124.    * restart to cancel the app disabled operation for the item otherwise the
  5125.    * property value will be set to OP_NEEDS_ENABLE. The item's pending
  5126.    * operations are then evaluated in order to set the operation to perform
  5127.    * and notify the observers if the operation has been changed.
  5128.    * See "Note on appDisabled and userDisabled property arcs" above.
  5129.    * @param   id
  5130.    *          The ID of the item to be enabled by the application.
  5131.    */
  5132.   _appEnableItem: function EM__appEnableItem(id) {
  5133.     var ds = this.datasource;
  5134.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5135.     if (appDisabled == OP_NONE || appDisabled == OP_NEEDS_ENABLE)
  5136.       return;
  5137.  
  5138.     var opType = ds.getItemProperty(id, "opType");
  5139.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5140.     // reset user disabled if it has a pending operation to prevent the ui
  5141.     // state from getting confused as to an item's current state.
  5142.     if (userDisabled == OP_NEEDS_DISABLE)
  5143.       ds.setItemProperty(id, EM_R("userDisabled"), null);
  5144.     else if (userDisabled == OP_NEEDS_ENABLE)
  5145.       ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  5146.  
  5147.     if (appDisabled == OP_NEEDS_DISABLE)
  5148.       ds.setItemProperty(id, EM_R("appDisabled"), null);
  5149.     else if (appDisabled == "true")
  5150.       ds.setItemProperty(id, EM_R("appDisabled"), EM_L(OP_NEEDS_ENABLE));
  5151.  
  5152.     // Don't set a new operation when there is a pending uninstall operation.
  5153.     if (opType == OP_NEEDS_UNINSTALL) {
  5154.       this._updateDependentItemsForID(id);
  5155.       return;
  5156.     }
  5157.  
  5158.     var operation, action;
  5159.     // if this item is already enabled or user disabled don't set a pending
  5160.     // operation - instead immediately enable it and reset the operation type
  5161.     // if needed.
  5162.     if (appDisabled == OP_NEEDS_DISABLE || appDisabled == OP_NONE ||
  5163.         userDisabled == "true") {
  5164.       if (opType != OP_NONE) {
  5165.         operation = OP_NONE;
  5166.         action = EM_ITEM_CANCEL;
  5167.       }
  5168.     }
  5169.     else {
  5170.       if (opType != OP_NEEDS_ENABLE) {
  5171.         operation = OP_NEEDS_ENABLE;
  5172.         action = EM_ITEM_ENABLED;
  5173.       }
  5174.     }
  5175.  
  5176.     if (action) {
  5177.       this._setOp(id, operation);
  5178.       this._notifyAction(id, action);
  5179.     }
  5180.     else {
  5181.       ds.updateProperty(id, "satisfiesDependencies");
  5182.       this._updateDependentItemsForID(id);
  5183.     }
  5184.   },
  5185.  
  5186.   /**
  5187.    * Disables an item for the application (e.g. the item doesn't satisfy all
  5188.    * requirements like app compatibility for it to be enabled). The appDisabled
  5189.    * property arc will be set to true if the item will be app enabled on next
  5190.    * restart to cancel the app enabled operation for the item otherwise the
  5191.    * property value will be set to OP_NEEDS_DISABLE. The item's pending
  5192.    * operations are then evaluated in order to set the operation to perform
  5193.    * and notify the observers if the operation has been changed.
  5194.    * See "Note on appDisabled and userDisabled property arcs" above.
  5195.    * @param   id
  5196.    *          The ID of the item to be disabled by the application.
  5197.    */
  5198.   _appDisableItem: function EM__appDisableItem(id) {
  5199.     var ds = this.datasource;
  5200.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5201.     if (appDisabled == "true" || appDisabled == OP_NEEDS_DISABLE)
  5202.       return;
  5203.  
  5204.     var opType = ds.getItemProperty(id, "opType");
  5205.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5206.  
  5207.     // reset user disabled if it has a pending operation to prevent the ui
  5208.     // state from getting confused as to an item's current state.
  5209.     if (userDisabled == OP_NEEDS_DISABLE)
  5210.       ds.setItemProperty(id, EM_R("userDisabled"), null);
  5211.     else if (userDisabled == OP_NEEDS_ENABLE)
  5212.       ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  5213.  
  5214.     if (appDisabled == OP_NEEDS_ENABLE || userDisabled == OP_NEEDS_ENABLE ||
  5215.         ds.getItemProperty(id, "userDisabled") == "true")
  5216.       ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  5217.     else if (appDisabled == OP_NONE)
  5218.       ds.setItemProperty(id, EM_R("appDisabled"), EM_L(OP_NEEDS_DISABLE));
  5219.  
  5220.     // Don't set a new operation when there is a pending uninstall operation.
  5221.     if (opType == OP_NEEDS_UNINSTALL) {
  5222.       this._updateDependentItemsForID(id);
  5223.       return;
  5224.     }
  5225.  
  5226.     var operation, action;
  5227.     // if this item is already disabled don't set a pending operation - instead
  5228.     // immediately disable it and reset the operation type if needed.
  5229.     if (appDisabled == OP_NEEDS_ENABLE || appDisabled == "true" ||
  5230.         userDisabled == OP_NEEDS_ENABLE || userDisabled == "true") {
  5231.       if (opType != OP_NONE) {
  5232.         operation = OP_NONE;
  5233.         action = EM_ITEM_CANCEL;
  5234.       }
  5235.     }
  5236.     else {
  5237.       if (opType != OP_NEEDS_DISABLE) {
  5238.         operation = OP_NEEDS_DISABLE;
  5239.         action = EM_ITEM_DISABLED;
  5240.       }
  5241.     }
  5242.  
  5243.     if (action) {
  5244.       this._setOp(id, operation);
  5245.       this._notifyAction(id, action);
  5246.     }
  5247.     else {
  5248.       ds.updateProperty(id, "satisfiesDependencies");
  5249.       this._updateDependentItemsForID(id);
  5250.     }
  5251.   },
  5252.  
  5253.   /**
  5254.    * Sets an item to be enabled by the user. If the item is already enabled this
  5255.    * clears the needs-enable operation for the next restart.
  5256.    * See "Note on appDisabled and userDisabled property arcs" above.
  5257.    * @param   id
  5258.    *          The ID of the item to be enabled by the user.
  5259.    */
  5260.   enableItem: function EM_enableItem(id) {
  5261.     var ds = this.datasource;
  5262.     var opType = ds.getItemProperty(id, "opType");
  5263.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5264.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5265.  
  5266.     var operation, action;
  5267.     // if this item is already enabled don't set a pending operation - instead
  5268.     // immediately enable it and reset the operation type if needed.
  5269.     if (appDisabled == OP_NONE &&
  5270.         userDisabled == OP_NEEDS_DISABLE || userDisabled == OP_NONE) {
  5271.       if (userDisabled == OP_NEEDS_DISABLE)
  5272.         ds.setItemProperty(id, EM_R("userDisabled"), null);
  5273.       if (opType != OP_NONE) {
  5274.         operation = OP_NONE;
  5275.         action = EM_ITEM_CANCEL;
  5276.       }
  5277.     }
  5278.     else {
  5279.       if (userDisabled == "true")
  5280.         ds.setItemProperty(id, EM_R("userDisabled"), EM_L(OP_NEEDS_ENABLE));
  5281.       if (opType != OP_NEEDS_ENABLE) {
  5282.         operation = OP_NEEDS_ENABLE;
  5283.         action = EM_ITEM_ENABLED;
  5284.       }
  5285.     }
  5286.  
  5287.     if (action) {
  5288.       this._setOp(id, operation);
  5289.       this._notifyAction(id, action);
  5290.     }
  5291.     else {
  5292.       ds.updateProperty(id, "satisfiesDependencies");
  5293.       this._updateDependentItemsForID(id);
  5294.     }
  5295.   },
  5296.  
  5297.   /**
  5298.    * Sets an item to be disabled by the user. If the item is already disabled
  5299.    * this clears the needs-disable operation for the next restart.
  5300.    * See "Note on appDisabled and userDisabled property arcs" above.
  5301.    * @param   id
  5302.    *          The ID of the item to be disabled by the user.
  5303.    */
  5304.   disableItem: function EM_disableItem(id) {
  5305.     var ds = this.datasource;
  5306.     var opType = ds.getItemProperty(id, "opType");
  5307.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5308.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5309.  
  5310.     var operation, action;
  5311.     // if this item is already disabled don't set a pending operation - instead
  5312.     // immediately disable it and reset the operation type if needed.
  5313.     if (userDisabled == OP_NEEDS_ENABLE || userDisabled == "true" ||
  5314.         appDisabled == OP_NEEDS_ENABLE) {
  5315.       if (userDisabled != "true")
  5316.         ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  5317.       if (opType != OP_NONE) {
  5318.         operation = OP_NONE;
  5319.         action = EM_ITEM_CANCEL;
  5320.       }
  5321.     }
  5322.     else {
  5323.       if (userDisabled == OP_NONE)
  5324.         ds.setItemProperty(id, EM_R("userDisabled"), EM_L(OP_NEEDS_DISABLE));
  5325.       if (opType != OP_NEEDS_DISABLE) {
  5326.         operation = OP_NEEDS_DISABLE;
  5327.         action = EM_ITEM_DISABLED;
  5328.       }
  5329.     }
  5330.  
  5331.     if (action) {
  5332.       this._setOp(id, operation);
  5333.       this._notifyAction(id, action);
  5334.     }
  5335.     else {
  5336.       ds.updateProperty(id, "satisfiesDependencies");
  5337.       this._updateDependentItemsForID(id);
  5338.     }
  5339.   },
  5340.  
  5341.   /**
  5342.    * Determines whether an item should be disabled by the application.
  5343.    * @param   id
  5344.    *          The ID of the item to check
  5345.    */
  5346.   _isUsableItem: function EM__isUsableItem(id) {
  5347.     var ds = this.datasource;
  5348.     /* If we're not compatibility checking or if the item is compatible
  5349.      * and if it isn't blocklisted and has all dependencies satisfied then
  5350.      * proceed to the security check */
  5351.     if ((!gCheckCompatibility || ds.getItemProperty(id, "compatible") == "true") &&
  5352.         ds.getItemProperty(id, "blocklisted") == "false" &&
  5353.         ds.getItemProperty(id, "satisfiesDependencies") == "true") {
  5354.  
  5355.       // appManaged items aren't updated so no need to check update security.
  5356.       if (ds.getItemProperty(id, "appManaged") == "true")
  5357.         return true;
  5358.  
  5359.       /* If we are not ignoring update security then check that the item has
  5360.        * a secure update mechanism */
  5361.       return (!gCheckUpdateSecurity ||
  5362.               ds.getItemProperty(id, "providesUpdatesSecurely") == "true");
  5363.     }
  5364.     return false;
  5365.   },
  5366.  
  5367.   /**
  5368.    * Sets an item's dependent items disabled state for the app based on whether
  5369.    * its dependencies are met and the item is compatible.
  5370.    * @param   id
  5371.    *          The ID of the item whose dependent items will be checked
  5372.    */
  5373.   _updateDependentItemsForID: function EM__updateDependentItemsForID(id) {
  5374.     var ds = this.datasource;
  5375.     var dependentItems = this.getDependentItemListForID(id, true, { });
  5376.     for (var i = 0; i < dependentItems.length; ++i) {
  5377.       var dependentID = dependentItems[i].id;
  5378.       ds.updateProperty(dependentID, "satisfiesDependencies");
  5379.       if (this._isUsableItem(dependentID))
  5380.         this._appEnableItem(dependentID);
  5381.       else
  5382.         this._appDisableItem(dependentID);
  5383.     }
  5384.   },
  5385.  
  5386.   /**
  5387.    * Notify observers of a change to an item that has been requested by the
  5388.    * user.
  5389.    */
  5390.   _notifyAction: function EM__notifyAction(id, reason) {
  5391.     gOS.notifyObservers(this.datasource.getItemForID(id),
  5392.                         EM_ACTION_REQUESTED_TOPIC, reason);
  5393.   },
  5394.  
  5395.   /**
  5396.    * See nsIExtensionManager.idl
  5397.    */
  5398.   update: function EM_update(items, itemCount, updateCheckType, listener,
  5399.                              appVersion, platformVersion) {
  5400.     for (i = 0; i < itemCount; ++i) {
  5401.       var currItem = items[i];
  5402.       if (!currItem)
  5403.         throw Cr.NS_ERROR_ILLEGAL_VALUE;
  5404.     }
  5405.  
  5406.     if (items.length == 0)
  5407.       items = this.getItemList(Ci.nsIUpdateItem.TYPE_ANY, { });
  5408.  
  5409.     var updater = new ExtensionItemUpdater(this);
  5410.     updater.checkForUpdates(items, items.length, updateCheckType, listener,
  5411.                             appVersion, platformVersion);
  5412.   },
  5413.  
  5414.   /**
  5415.    * See nsIExtensionManager.idl
  5416.    */
  5417.   updateAndGetNewBlocklistedItems: function EM_updateAndGetNewBlocklistedItems(itemCount) {
  5418.     if (!gBlocklist)
  5419.       gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
  5420.                    getService(Ci.nsIBlocklistService);
  5421.  
  5422.     var list = [];
  5423.     var ds = this.datasource;
  5424.     var items = this.getItemList(Ci.nsIUpdateItem.TYPE_ANY, { });
  5425.     for (var i = 0; i < items.length; ++i) {
  5426.       var id = items[i].id;
  5427.  
  5428.       // Get whether the add-on is currently disabled or set to be disabled.
  5429.       var appDisabled = (ds.getItemProperty(id, "appDisabled") == "true" ||
  5430.                          ds.getItemProperty(id, "appDisabled") == OP_NEEDS_DISABLE);
  5431.       var userDisabled = (ds.getItemProperty(id, "userDisabled") == "true" ||
  5432.                           ds.getItemProperty(id, "userDisabled") == OP_NEEDS_DISABLE);
  5433.       var usable = this._isUsableItem(id);
  5434.       var state = gBlocklist.getAddonBlocklistState(items[i].id, items[i].version);
  5435.  
  5436.       // We only return items that are now blocked or to be warned about and aren't
  5437.       // already disabled for some reason.
  5438.       if (!appDisabled && !userDisabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
  5439.         list.push(items[i]);
  5440.  
  5441.       // Update the appDisabled status based on the new blocked state
  5442.       if (usable)
  5443.         this._appEnableItem(id);
  5444.       else
  5445.         this._appDisableItem(id);
  5446.  
  5447.       // If the item was appDisabled and is now usable then it is something
  5448.       // that is no longer hard blocked. If it is still to be warned about then
  5449.       // just user disable it.
  5450.       if (appDisabled && usable && !userDisabled &&
  5451.           state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
  5452.         this.disableItem(id);
  5453.  
  5454.       ds.updateProperty(id, "blocklisted");
  5455.       ds.updateProperty(id, "blocklistedsoft");
  5456.     }
  5457.  
  5458.     itemCount.value = list.length;
  5459.     return list;
  5460.   },
  5461.  
  5462.   /**
  5463.    * @returns An enumeration of all registered Install Locations.
  5464.    */
  5465.   get installLocations () {
  5466.     return InstallLocations.enumeration;
  5467.   },
  5468.  
  5469.   /**
  5470.    * Gets the Install Location where a visible Item is stored.
  5471.    * @param   id
  5472.    *          The GUID of the item to locate an Install Location for.
  5473.    * @returns The Install Location object where the item is stored.
  5474.    */
  5475.   getInstallLocation: function EM_getInstallLocation(id) {
  5476.     var key = this.datasource.visibleItems[id];
  5477.     return key ? InstallLocations.get(this.datasource.visibleItems[id]) : null;
  5478.   },
  5479.  
  5480.   /**
  5481.    * Gets a nsIUpdateItem for the item with the specified id.
  5482.    * @param   id
  5483.    *          The GUID of the item to construct a nsIUpdateItem for.
  5484.    * @returns The nsIUpdateItem representing the item.
  5485.    */
  5486.   getItemForID: function EM_getItemForID(id) {
  5487.     return this.datasource.getItemForID(id);
  5488.   },
  5489.  
  5490.   /**
  5491.    * Retrieves a list of installed nsIUpdateItems of items that are dependent
  5492.    * on another item.
  5493.    * @param   id
  5494.    *          The ID of the item that other items depend on.
  5495.    * @param   includeDisabled
  5496.    *          Whether to include disabled items in the set returned.
  5497.    * @param   countRef
  5498.    *          The XPCJS reference to the number of items returned.
  5499.    * @returns An array of installed nsIUpdateItems that depend on the item
  5500.    *          specified by the id parameter.
  5501.    */
  5502.   getDependentItemListForID: function EM_getDependentItemListForID(id,
  5503.                                                                    includeDisabled,
  5504.                                                                    countRef) {
  5505.     return this.datasource.getDependentItemListForID(id, includeDisabled, countRef);
  5506.   },
  5507.  
  5508.   /* See nsIExtensionManager.idl */
  5509.   getItemList: function EM_getItemList(type, countRef) {
  5510.     return this.datasource.getItemList(type, countRef);
  5511.   },
  5512.  
  5513.   /* See nsIExtensionManager.idl */
  5514.   getIncompatibleItemList: function EM_getIncompatibleItemList(id, appVersion,
  5515.                                                                platformVersion,
  5516.                                                                type,
  5517.                                                                includeDisabled,
  5518.                                                                countRef) {
  5519.     var items = this.datasource.getIncompatibleItemList(id, appVersion ? appVersion : undefined,
  5520.                                                         platformVersion ? platformVersion : undefined,
  5521.                                                         type, includeDisabled);
  5522.     countRef.value = items.length;
  5523.     return items;
  5524.   },
  5525.  
  5526.   /**
  5527.    * Move an Item to the index of another item in its container.
  5528.    * @param   movingID
  5529.    *          The ID of the item to be moved.
  5530.    * @param   destinationID
  5531.    *          The ID of an item to move another item to.
  5532.    */
  5533.   moveToIndexOf: function EM_moveToIndexOf(movingID, destinationID) {
  5534.     this.datasource.moveToIndexOf(movingID, destinationID);
  5535.   },
  5536.  
  5537.   /**
  5538.    * Sorts addons of the specified type by the specified property starting from
  5539.    * the top of their container. If the addons are already sorted then no action
  5540.    * is performed.
  5541.    * @param   type
  5542.    *          The nsIUpdateItem type of the items to sort.
  5543.    * @param   propertyName
  5544.    *          The RDF property name used for sorting.
  5545.    * @param   isAscending
  5546.    *          true to sort ascending and false to sort descending
  5547.    */
  5548.   sortTypeByProperty: function EM_sortTypeByProperty(type, propertyName, isAscending) {
  5549.     this.datasource.sortTypeByProperty(type, propertyName, isAscending);
  5550.   },
  5551.  
  5552.   /////////////////////////////////////////////////////////////////////////////
  5553.   // Downloads
  5554.   _transactions: [],
  5555.   _downloadCount: 0,
  5556.   _compatibilityCheckCount: 0,
  5557.  
  5558.   /**
  5559.    * Ask the user if they really want to quit the application, since this will
  5560.    * cancel one or more Extension/Theme downloads.
  5561.    * @param   subject
  5562.    *          A nsISupportsPRBool which this function sets to false if the user
  5563.    *          wishes to cancel all active downloads and quit the application,
  5564.    *          false otherwise.
  5565.    */
  5566.   _confirmCancelDownloadsOnQuit: function EM__confirmCancelDownloadsOnQuit(subject) {
  5567.     // If user has already dismissed quit request, then do nothing
  5568.     if ((subject instanceof Ci.nsISupportsPRBool) && subject.data)
  5569.       return;
  5570.  
  5571.     if (this._downloadCount > 0) {
  5572.       // The observers will be notified again after this so set the download
  5573.       // count to 0 to prevent this dialog from being displayed again.
  5574.       this._downloadCount = 0;
  5575.       var result;
  5576. //@line 5704 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  5577.       result = this._confirmCancelDownloads(this._downloadCount,
  5578.                                             "quitCancelDownloadsAlertTitle",
  5579.                                             "quitCancelDownloadsAlertMsgMultiple",
  5580.                                             "quitCancelDownloadsAlertMsg",
  5581.                                             "dontQuitButtonWin");
  5582. //@line 5716 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  5583.       if (subject instanceof Ci.nsISupportsPRBool)
  5584.         subject.data = result;
  5585.     }
  5586.   },
  5587.  
  5588.   /**
  5589.    * Ask the user if they really want to go offline, since this will cancel
  5590.    * one or more Extension/Theme downloads.
  5591.    * @param   subject
  5592.    *          A nsISupportsPRBool which this function sets to false if the user
  5593.    *          wishes to cancel all active downloads and go offline, false
  5594.    *          otherwise.
  5595.    */
  5596.   _confirmCancelDownloadsOnOffline: function EM__confirmCancelDownloadsOnOffline(subject) {
  5597.     if (this._downloadCount > 0) {
  5598.       result = this._confirmCancelDownloads(this._downloadCount,
  5599.                                             "offlineCancelDownloadsAlertTitle",
  5600.                                             "offlineCancelDownloadsAlertMsgMultiple",
  5601.                                             "offlineCancelDownloadsAlertMsg",
  5602.                                             "dontGoOfflineButton");
  5603.       if (subject instanceof Ci.nsISupportsPRBool)
  5604.         subject.data = result;
  5605.     }
  5606.   },
  5607.  
  5608.   /**
  5609.    * Ask the user whether or not they wish to cancel the Extension/Theme
  5610.    * downloads which are currently under way.
  5611.    * @param   count
  5612.    *          The number of active downloads.
  5613.    * @param   title
  5614.    *          The key of the title for the message box to be displayed
  5615.    * @param   cancelMessageMultiple
  5616.    *          The key of the message to be displayed in the message box
  5617.    *          when there are > 1 active downloads.
  5618.    * @param   cancelMessageSingle
  5619.    *          The key of the message to be displayed in the message box
  5620.    *          when there is just one active download.
  5621.    * @param   dontCancelButton
  5622.    *          The key of the label to be displayed on the "Don't Cancel
  5623.    *          Downloads" button.
  5624.    */
  5625.   _confirmCancelDownloads: function EM__confirmCancelDownloads(count, title,
  5626.                                                                cancelMessageMultiple,
  5627.                                                                cancelMessageSingle,
  5628.                                                                dontCancelButton) {
  5629.     var bundle = BundleManager.getBundle(URI_DOWNLOADS_PROPERTIES);
  5630.     var title = bundle.GetStringFromName(title);
  5631.     var message, quitButton;
  5632.     if (count > 1) {
  5633.       message = bundle.formatStringFromName(cancelMessageMultiple, [count], 1);
  5634.       quitButton = bundle.formatStringFromName("cancelDownloadsOKTextMultiple", [count], 1);
  5635.     }
  5636.     else {
  5637.       message = bundle.GetStringFromName(cancelMessageSingle);
  5638.       quitButton = bundle.GetStringFromName("cancelDownloadsOKText");
  5639.     }
  5640.     var dontQuitButton = bundle.GetStringFromName(dontCancelButton);
  5641.  
  5642.     var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  5643.              getService(Ci.nsIWindowMediator);
  5644.     var win = wm.getMostRecentWindow("Extension:Manager");
  5645.     const nsIPromptService = Ci.nsIPromptService;
  5646.     var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  5647.              getService(nsIPromptService);
  5648.     var flags = (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_0) +
  5649.                 (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_1);
  5650.     var rv = ps.confirmEx(win, title, message, flags, quitButton, dontQuitButton, null, null, { });
  5651.     return rv == 1;
  5652.   },
  5653.  
  5654.   /* See nsIExtensionManager.idl */
  5655.   addDownloads: function EM_addDownloads(items, itemCount, manager) {
  5656.     if (itemCount == 0)
  5657.       throw Cr.NS_ERROR_ILLEGAL_VALUE;
  5658.  
  5659.     for (i = 0; i < itemCount; ++i) {
  5660.       var currItem = items[i];
  5661.       if (!currItem)
  5662.         throw Cr.NS_ERROR_ILLEGAL_VALUE;
  5663.     }
  5664.  
  5665.     var ds = this.datasource;
  5666.     // Add observers only if they aren't already added for an active download
  5667.     if (this._downloadCount == 0) {
  5668.       gOS.addObserver(this, "offline-requested", false);
  5669.       gOS.addObserver(this, "quit-application-requested", false);
  5670.     }
  5671.     this._downloadCount += itemCount;
  5672.  
  5673.     var urls = [];
  5674.     var hashes = [];
  5675.     var txnID = Math.round(Math.random() * 100);
  5676.     var txn = new ItemDownloadTransaction(this, txnID);
  5677.     for (var i = 0; i < itemCount; ++i) {
  5678.       var currItem = items[i];
  5679.  
  5680.       txn.addDownload(currItem);
  5681.       urls.push(currItem.xpiURL);
  5682.       hashes.push(currItem.xpiHash ? currItem.xpiHash : null);
  5683.       // if this is an update remove the update metadata to prevent it from
  5684.       // being updated during an install.
  5685.       if (!manager) {
  5686.         var id = currItem.id
  5687.         ds.setItemProperties(id, {
  5688.           availableUpdateURL: null,
  5689.           availableUpdateHash: null,
  5690.           availableUpdateVersion: null,
  5691.           availableUpdateInfo: null
  5692.         });
  5693.         ds.updateProperty(id, "availableUpdateURL");
  5694.         ds.updateProperty(id, "updateable");
  5695.       }
  5696.       var id = !manager ? PREFIX_ITEM_URI + currItem.id : currItem.xpiURL;
  5697.       ds.updateDownloadState(id, "waiting");
  5698.     }
  5699.     this._transactions.push(txn);
  5700.  
  5701.     if (manager) {
  5702.       // XPIManager initiated -- let it know we're ready
  5703.       manager.observe(txn, "xpinstall-progress", "open");
  5704.     }
  5705.     else {
  5706.       // Initiate an install from chrome
  5707.       var xpimgr = Cc["@mozilla.org/xpinstall/install-manager;1"].
  5708.                    createInstance(Ci.nsIXPInstallManager);
  5709.       xpimgr.initManagerWithHashes(urls, hashes, urls.length, txn);
  5710.     }
  5711.   },
  5712.  
  5713.   /**
  5714.    * Download Operation State has changed from one to another.
  5715.    *
  5716.    * The nsIXPIProgressDialog implementation in the download transaction object
  5717.    * forwards notifications through these methods which we then pass on to any
  5718.    * front end objects implementing nsIExtensionDownloadListener that
  5719.    * are listening. We maintain the master state of download operations HERE,
  5720.    * not in the front end, because if the user closes the extension or theme
  5721.    * managers during the downloads we need to maintain state and not terminate
  5722.    * the download/install process.
  5723.    *
  5724.    * @param   transaction
  5725.    *          The ItemDownloadTransaction object receiving the download
  5726.    *          notifications from XPInstall.
  5727.    * @param   addon
  5728.    *          An object representing nsIUpdateItem for the addon being updated
  5729.    * @param   state
  5730.    *          The state we are entering
  5731.    * @param   value
  5732.    *          ???
  5733.    */
  5734.   onStateChange: function EM_onStateChange(transaction, addon, state, value) {
  5735.     var ds = this.datasource;
  5736.     var id = addon.id != addon.xpiURL ? PREFIX_ITEM_URI + addon.id : addon.xpiURL;
  5737.     const nsIXPIProgressDialog = Ci.nsIXPIProgressDialog;
  5738.     switch (state) {
  5739.     case nsIXPIProgressDialog.DOWNLOAD_START:
  5740.       ds.updateDownloadState(id, "downloading");
  5741.       for (var i = 0; i < this._installListeners.length; ++i)
  5742.         this._installListeners[i].onDownloadStarted(addon);
  5743.       break;
  5744.     case nsIXPIProgressDialog.DOWNLOAD_DONE:
  5745.       for (var i = 0; i < this._installListeners.length; ++i)
  5746.         this._installListeners[i].onDownloadEnded(addon);
  5747.       break;
  5748.     case nsIXPIProgressDialog.INSTALL_START:
  5749.       ds.updateDownloadState(id, "finishing");
  5750.       ds.updateDownloadProgress(id, null);
  5751.       break;
  5752.     case nsIXPIProgressDialog.INSTALL_DONE:
  5753.       --this._downloadCount;
  5754.       // From nsInstall.h
  5755.       // SUCCESS        = 0
  5756.       // USER_CANCELLED = -210
  5757.       if (value != 0 && value != -210 && id != addon.xpiURL) {
  5758.         ds.updateDownloadState(id, "failure");
  5759.         ds.updateDownloadProgress(id, null);
  5760.       }
  5761.       transaction.removeDownload(addon.xpiURL);
  5762.       // A successful install will be passing notifications via installItemFromFile
  5763.       if (value != 0) {
  5764.         for (var i = 0; i < this._installListeners.length; ++i)
  5765.           this._installListeners[i].onInstallEnded(addon, value);
  5766.       }
  5767.       break;
  5768.     case nsIXPIProgressDialog.DIALOG_CLOSE:
  5769.       for (var i = 0; i < this._transactions.length; ++i) {
  5770.         if (this._transactions[i].id == transaction.id) {
  5771.           this._transactions.splice(i, 1);
  5772.           // Remove the observers when all transactions have completed.
  5773.           if (this._transactions.length == 0) {
  5774.             gOS.removeObserver(this, "offline-requested");
  5775.             gOS.removeObserver(this, "quit-application-requested");
  5776.  
  5777.             // If there are no compatibility checks running then the install
  5778.             // operations are complete.
  5779.             if (this._compatibilityCheckCount == 0) {
  5780.               for (var i = 0; i < this._installListeners.length; ++i)
  5781.                 this._installListeners[i].onInstallsCompleted();
  5782.             }
  5783.           }
  5784.           break;
  5785.         }
  5786.       }
  5787.       // Remove any remaining downloads from this transaction
  5788.       transaction.removeAllDownloads();
  5789.       break;
  5790.     }
  5791.   },
  5792.  
  5793.   onProgress: function EM_onProgress(addon, value, maxValue) {
  5794.     for (var i = 0; i < this._installListeners.length; ++i)
  5795.       this._installListeners[i].onDownloadProgress(addon, value, maxValue);
  5796.  
  5797.     var id = addon.id != addon.xpiURL ? PREFIX_ITEM_URI + addon.id : addon.xpiURL;
  5798.     var progress = Math.round((value / maxValue) * 100);
  5799.     this.datasource.updateDownloadProgress(id, progress);
  5800.   },
  5801.  
  5802.   _installListeners: [],
  5803.   addInstallListener: function EM_addInstallListener(listener) {
  5804.     for (var i = 0; i < this._installListeners.length; ++i) {
  5805.       if (this._installListeners[i] == listener)
  5806.         return i;
  5807.     }
  5808.     this._installListeners.push(listener);
  5809.     return this._installListeners.length - 1;
  5810.   },
  5811.  
  5812.   removeInstallListenerAt: function EM_removeInstallListenerAt(index) {
  5813.     this._installListeners.splice(index, 1);
  5814.   },
  5815.  
  5816.   /**
  5817.    * The Extensions RDF Datasource
  5818.    */
  5819.   _ds: null,
  5820.   _ptr: null,
  5821.  
  5822.   /**
  5823.    * Loads the Extensions Datasource. This should not be called unless:
  5824.    * - a piece of Extensions UI is being shown, or
  5825.    * - on startup and there has been a change to an Install Location
  5826.    * ... it should NOT be called on every startup!
  5827.    */
  5828.   _ensureDS: function EM__ensureDS() {
  5829.     if (!this._ds) {
  5830.       this._ds = new ExtensionsDataSource(this);
  5831.       if (this._ds) {
  5832.         this._ds.loadExtensions();
  5833.         this._ptr = getContainer(this._ds, this._ds._itemRoot).DataSource;
  5834.         gRDF.RegisterDataSource(this._ptr, true);
  5835.       }
  5836.     }
  5837.   },
  5838.  
  5839.   /**
  5840.    * See nsIExtensionManager.idl
  5841.    */
  5842.   get datasource() {
  5843.     this._ensureDS();
  5844.     return this._ds;
  5845.   },
  5846.  
  5847.   // nsIClassInfo
  5848.   flags: Ci.nsIClassInfo.SINGLETON,
  5849.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  5850.   getHelperForLanguage: function(language) null,
  5851.   getInterfaces: function(count) {
  5852.     var interfaces = [Ci.nsIExtensionManager, Ci.nsIObserver];
  5853.     count.value = interfaces.length;
  5854.     return interfaces;
  5855.   },
  5856.  
  5857.   classDescription: "Extension Manager",
  5858.   contractID: "@mozilla.org/extensions/manager;1",
  5859.   classID: Components.ID("{8A115FAA-7DCB-4e8f-979B-5F53472F51CF}"),
  5860.   _xpcom_categories: [{ category: "profile-after-change" }],
  5861.   _xpcom_factory: {
  5862.     createInstance: function(outer, iid) {
  5863.       if (outer != null)
  5864.         throw Cr.NS_ERROR_NO_AGGREGATION;
  5865.   
  5866.       if (!gEmSingleton)
  5867.         gEmSingleton = new ExtensionManager();
  5868.       return gEmSingleton.QueryInterface(iid);
  5869.     }
  5870.   },
  5871.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIExtensionManager,
  5872.                                          Ci.nsITimerCallback,
  5873.                                          Ci.nsIObserver,
  5874.                                          Ci.nsIClassInfo])
  5875. };
  5876.  
  5877. /**
  5878.  * This object implements nsIXPIProgressDialog and represents a collection of
  5879.  * XPI/JAR download and install operations. There is one
  5880.  * ItemDownloadTransaction per back-end XPInstallManager object. We maintain
  5881.  * a collection of separate transaction objects because it's possible to have
  5882.  * multiple separate XPInstall download/install operations going on
  5883.  * simultaneously, each with its own XPInstallManager instance. For instance
  5884.  * you could start downloading two extensions and then download a theme. Each
  5885.  * of these operations would open the appropriate FE and have to be able to
  5886.  * track each operation independently.
  5887.  *
  5888.  * @constructor
  5889.  * @param   manager
  5890.  *          The extension manager creating this transaction
  5891.  * @param   id
  5892.  *          The integer identifier of this transaction
  5893.  */
  5894. function ItemDownloadTransaction(manager, id) {
  5895.   this._manager = manager;
  5896.   this._downloads = [];
  5897.   this.id = id;
  5898. }
  5899. ItemDownloadTransaction.prototype = {
  5900.   _manager    : null,
  5901.   _downloads  : [],
  5902.   id          : -1,
  5903.  
  5904.   /**
  5905.    * Add a download to this transaction
  5906.    * @param   addon
  5907.    *          An object implementing nsIUpdateItem for the item to be downloaded
  5908.    */
  5909.   addDownload: function ItemDownloadTransaction_addDownload(addon) {
  5910.     this._downloads.push({ addon: addon, waiting: true });
  5911.     this._manager.datasource.addDownload(addon);
  5912.   },
  5913.  
  5914.   /**
  5915.    * Removes a download from this transaction
  5916.    * @param   url
  5917.    *          The URL to remove
  5918.    */
  5919.   removeDownload: function ItemDownloadTransaction_removeDownload(url) {
  5920.     this._manager.datasource.removeDownload(url);
  5921.   },
  5922.  
  5923.   /**
  5924.    * Remove all downloads from this transaction
  5925.    */
  5926.   removeAllDownloads: function ItemDownloadTransaction_removeAllDownloads() {
  5927.     for (var i = 0; i < this._downloads.length; ++i) {
  5928.       var addon = this._downloads[i].addon;
  5929.       this.removeDownload(addon.xpiURL);
  5930.     }
  5931.   },
  5932.  
  5933.   /**
  5934.    * Determine if this transaction is handling the download of a url.
  5935.    * @param   url
  5936.    *          The URL to look for
  5937.    * @returns true if this transaction is downloading the supplied url.
  5938.    */
  5939.   containsURL: function ItemDownloadTransaction_containsURL(url) {
  5940.     for (var i = 0; i < this._downloads.length; ++i) {
  5941.       if (this._downloads[i].addon.xpiURL == url)
  5942.         return true;
  5943.     }
  5944.     return false;
  5945.   },
  5946.  
  5947.   /**
  5948.    * See nsIXPIProgressDialog.idl
  5949.    */
  5950.   onStateChange: function ItemDownloadTransaction_onStateChange(index, state, value) {
  5951.     this._manager.onStateChange(this, this._downloads[index].addon,
  5952.                                 state, value);
  5953.   },
  5954.  
  5955.   /**
  5956.    * See nsIXPIProgressDialog.idl
  5957.    */
  5958.   onProgress: function ItemDownloadTransaction_onProgress(index, value, maxValue) {
  5959.     this._manager.onProgress(this._downloads[index].addon, value, maxValue);
  5960.   },
  5961.  
  5962.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIXPIProgressDialog])
  5963. };
  5964.  
  5965. /**
  5966.  * A listener object that watches the background update check and notifies the
  5967.  * user of any updates found.
  5968.  */
  5969. function BackgroundUpdateCheckListener(datasource) {
  5970.   this._emDS = datasource;
  5971. }
  5972. BackgroundUpdateCheckListener.prototype = {
  5973.   _updateCount: 0,
  5974.   _emDS: null,
  5975.  
  5976.   // nsIObserver implementation
  5977.   observe: function BackgroundUpdateListener_observe(aSubject, aTopic, aData) {
  5978.     if (aTopic != "alertclickcallback")
  5979.       return;
  5980.  
  5981.     var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  5982.              getService(Ci.nsIWindowMediator);
  5983.     var win = wm.getMostRecentWindow("Extension:Manager");
  5984.     if (win) {
  5985.       win.focus();
  5986.       win.showView("updates");
  5987.       // Don't show the update notification on next startup
  5988.       gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false);
  5989.     }
  5990.     else {
  5991.       const EMURL = "chrome://mozapps/content/extensions/extensions.xul";
  5992.       const EMFEATURES = "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable";
  5993.  
  5994.       var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  5995.                getService(Ci.nsIWindowWatcher);
  5996.       var param = Cc["@mozilla.org/supports-array;1"].
  5997.                   createInstance(Ci.nsISupportsArray);
  5998.       var arg = Cc["@mozilla.org/supports-string;1"].
  5999.                 createInstance(Ci.nsISupportsString);
  6000.       arg.data = "updates";
  6001.       param.AppendElement(arg);
  6002.       ww.openWindow(null, EMURL, null, EMFEATURES, param);
  6003.     }
  6004.   },
  6005.   
  6006.   // nsIAddonUpdateCheckListener implementation
  6007.   onUpdateStarted: function BackgroundUpdateListener_onUpdateStarted() {
  6008.   },
  6009.  
  6010.   onUpdateEnded: function BackgroundUpdateListener_onUpdateEnded() {
  6011.     if (this._updateCount > 0 && Cc["@mozilla.org/alerts-service;1"]) {
  6012.       var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  6013.       var title = extensionStrings.GetStringFromName("updateNotificationTitle");
  6014.       var text;
  6015.       if (this._updateCount > 1)
  6016.         text = extensionStrings.formatStringFromName("multipleUpdateNotificationText",
  6017.                                                      [BundleManager.appName, this._updateCount], 2);
  6018.       else
  6019.         text = extensionStrings.formatStringFromName("updateNotificationText",
  6020.                                                      [BundleManager.appName], 1);
  6021.  
  6022.       try {
  6023.         var notifier = Cc["@mozilla.org/alerts-service;1"].
  6024.                        getService(Ci.nsIAlertsService);
  6025.         notifier.showAlertNotification(URI_GENERIC_ICON_XPINSTALL,
  6026.                                        title, text, true, "", this);
  6027.       }
  6028.       catch (e) {
  6029.         LOG("Failed to retrieve alerts service, probably an unsupported " +
  6030.             "platform - " + e);
  6031.       }
  6032.     }
  6033.   },
  6034.  
  6035.   onAddonUpdateStarted: function BackgroundUpdateListener_onAddonUpdateStarted(item) {
  6036.   },
  6037.  
  6038.   onAddonUpdateEnded: function BackgroundUpdateListener_onAddonUpdateEnded(item, status) {
  6039.     if (status == Ci.nsIAddonUpdateCheckListener.STATUS_UPDATE) {
  6040.       var lastupdate = this._emDS.getItemProperty(item.id, "availableUpdateVersion");
  6041.       if (lastupdate != item.version) {
  6042.         gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, true);
  6043.         this._updateCount++;
  6044.       }
  6045.     }
  6046.   }
  6047. };
  6048.  
  6049.  
  6050. /**
  6051.  * A listener object to the update check process that routes notifications to
  6052.  * the right places and keeps the datasource up to date.
  6053.  */
  6054. function AddonUpdateCheckListener(listener, datasource) {
  6055.   this._listener = listener;
  6056.   this._ds = datasource;
  6057. }
  6058. AddonUpdateCheckListener.prototype = {
  6059.   _listener: null,
  6060.   _ds: null,
  6061.  
  6062.   onUpdateStarted: function AddonUpdateListener_onUpdateStarted() {
  6063.     if (this._listener)
  6064.       this._listener.onUpdateStarted();
  6065.     this._ds.onUpdateStarted();
  6066.   },
  6067.  
  6068.   onUpdateEnded: function AddonUpdateListener_onUpdateEnded() {
  6069.     if (this._listener)
  6070.       this._listener.onUpdateEnded();
  6071.     this._ds.onUpdateEnded();
  6072.   },
  6073.  
  6074.   onAddonUpdateStarted: function AddonUpdateListener_onAddonUpdateStarted(addon) {
  6075.     if (this._listener)
  6076.       this._listener.onAddonUpdateStarted(addon);
  6077.     this._ds.onAddonUpdateStarted(addon);
  6078.   },
  6079.  
  6080.   onAddonUpdateEnded: function AddonUpdateListener_onAddonUpdateEnded(addon, status) {
  6081.     if (this._listener)
  6082.       this._listener.onAddonUpdateEnded(addon, status);
  6083.     this._ds.onAddonUpdateEnded(addon, status);
  6084.   }
  6085. };
  6086.  
  6087. ///////////////////////////////////////////////////////////////////////////////
  6088. //
  6089. // ExtensionItemUpdater
  6090. //
  6091. function ExtensionItemUpdater(aEM)
  6092. {
  6093.   this._emDS = aEM._ds;
  6094.   this._em = aEM;
  6095.  
  6096.   getVersionChecker();
  6097. }
  6098.  
  6099. ExtensionItemUpdater.prototype = {
  6100.   _emDS               : null,
  6101.   _em                 : null,
  6102.   _updateCheckType    : 0,
  6103.   _items              : [],
  6104.   _listener           : null,
  6105.  
  6106.   /* ExtensionItemUpdater
  6107. //@line 6266 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  6108.   */
  6109.   checkForUpdates: function ExtensionItemUpdater_checkForUpdates(aItems,
  6110.                                                                  aItemCount,
  6111.                                                                  aUpdateCheckType,
  6112.                                                                  aListener,
  6113.                                                                  aAppVersion,
  6114.                                                                  aPlatformVersion) {
  6115.     if (aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) {
  6116.       this._listener = aListener;
  6117.       this._appVersion = aAppVersion ? aAppVersion : gApp.version;
  6118.       this._platformVersion = aPlatformVersion ? aPlatformVersion
  6119.                                                : gApp.platformVersion;
  6120.     }
  6121.     else {
  6122.       this._listener = new AddonUpdateCheckListener(aListener, this._emDS);
  6123.       this._appVersion = gApp.version;
  6124.       this._platformVersion = gApp.platformVersion;
  6125.     }
  6126.  
  6127.     if (this._listener)
  6128.       this._listener.onUpdateStarted();
  6129.     this._updateCheckType = aUpdateCheckType;
  6130.     this._items = aItems;
  6131.     this._responseCount = aItemCount;
  6132.  
  6133.     // This is the number of extensions/themes/etc that we found updates for.
  6134.     this._updateCount = 0;
  6135.  
  6136.     for (var i = 0; i < aItemCount; ++i) {
  6137.       var e = this._items[i];
  6138.       if (this._listener)
  6139.         this._listener.onAddonUpdateStarted(e);
  6140.       (new RDFItemUpdater(this)).checkForUpdates(e, aUpdateCheckType);
  6141.     }
  6142.  
  6143.     if (this._listener && aItemCount == 0)
  6144.       this._listener.onUpdateEnded();
  6145.   },
  6146.  
  6147.   /////////////////////////////////////////////////////////////////////////////
  6148.   // ExtensionItemUpdater
  6149.   _applyVersionUpdates: function ExtensionItemUpdater__applyVersionUpdates(aLocalItem,
  6150.                                                                            aRemoteItem) {
  6151.     var targetAppInfo = this._emDS.getTargetApplicationInfo(aLocalItem.id, this._emDS);
  6152.     // If targetAppInfo is null this is for a new install. If the local item's
  6153.     // maxVersion does not equal the targetAppInfo maxVersion then this is for
  6154.     // an upgrade. In both of these cases return true if the remotely specified
  6155.     // maxVersion is greater than the local item's maxVersion.
  6156.     if (!targetAppInfo ||
  6157.         gVersionChecker.compare(aLocalItem.maxAppVersion, targetAppInfo.maxVersion) != 0) {
  6158.       if (gVersionChecker.compare(aLocalItem.maxAppVersion, aRemoteItem.maxAppVersion) < 0)
  6159.         return true;
  6160.       else
  6161.         return false;
  6162.     }
  6163.  
  6164.     if (gVersionChecker.compare(targetAppInfo.maxVersion, aRemoteItem.maxAppVersion) < 0) {
  6165.       // Remotely specified maxVersion is newer than the maxVersion
  6166.       // for the installed Extension. Apply that change to the datasources.
  6167.       this._emDS.setTargetApplicationInfo(aLocalItem.id,
  6168.                                           aRemoteItem.targetAppID,
  6169.                                           aRemoteItem.minAppVersion,
  6170.                                           aRemoteItem.maxAppVersion,
  6171.                                           null);
  6172.  
  6173.       // If we got here through |checkForMismatches|, this extension has
  6174.       // already been disabled, re-enable it.
  6175.       var op = StartupCache.entries[aLocalItem.installLocationKey][aLocalItem.id].op;
  6176.       if (op == OP_NEEDS_DISABLE ||
  6177.           this._emDS.getItemProperty(aLocalItem.id, "appDisabled") == "true")
  6178.         this._em._appEnableItem(aLocalItem.id);
  6179.       return true;
  6180.     }
  6181.     else if (this._updateCheckType == Ci.nsIExtensionManager.UPDATE_SYNC_COMPATIBILITY)
  6182.       this._emDS.setTargetApplicationInfo(aLocalItem.id,
  6183.                                           aRemoteItem.targetAppID,
  6184.                                           aRemoteItem.minAppVersion,
  6185.                                           aRemoteItem.maxAppVersion,
  6186.                                           null);
  6187.     return false;
  6188.   },
  6189.  
  6190.   /**
  6191.    * Checks whether a discovered update is valid for install
  6192.    * @param   aLocalItem
  6193.    *          The already installed nsIUpdateItem that the update is for
  6194.    * @param   aRemoteItem
  6195.    *          The nsIUpdateItem we are trying to update to
  6196.    * @param   aUpdateCheckType
  6197.    *          The type of update check being performed
  6198.    *
  6199.    * @returns true if the item is compatible and is not blocklisted.
  6200.    *          false if the item is not compatible or is blocklisted.
  6201.    */
  6202.   _isValidUpdate: function _isValidUpdate(aLocalItem, aRemoteItem, aUpdateCheckType) {
  6203.     var appExtensionsVersion = (aRemoteItem.targetAppID != TOOLKIT_ID) ?
  6204.                                this._appVersion :
  6205.                                this._platformVersion;
  6206.  
  6207.     var min = aRemoteItem.minAppVersion;
  6208.     var max = aRemoteItem.maxAppVersion;
  6209.     // Check if the update will only run on a newer version of the application.
  6210.     if (!min || gVersionChecker.compare(appExtensionsVersion, min) < 0)
  6211.       return false;
  6212.  
  6213.     // Check if the update will only run on an older version of the application.
  6214.     if (!max || gVersionChecker.compare(appExtensionsVersion, max) > 0)
  6215.       return false;
  6216.  
  6217.     // Ignore the blocklist for compatibility only checks.
  6218.     if (aUpdateCheckType != Ci.nsIExtensionManager.UPDATE_CHECK_COMPATIBILITY) {
  6219.       if (!gBlocklist)
  6220.         gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
  6221.                      getService(Ci.nsIBlocklistService);
  6222.       // Denies updates that are hard blocked, soft blocked items will be warned
  6223.       // about during the install.
  6224.       if (gBlocklist.isAddonBlocklisted(aLocalItem.id, aRemoteItem.version,
  6225.                                         this._appVersion, this._platformVersion))
  6226.         return false;
  6227.     }
  6228.  
  6229.     return true;
  6230.   },
  6231.  
  6232.   checkForDone: function ExtensionItemUpdater_checkForDone(item, status) {
  6233.     if (this._listener) {
  6234.       try {
  6235.         this._listener.onAddonUpdateEnded(item, status);
  6236.       }
  6237.       catch (e) {
  6238.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onAddonUpdateEnded: " + e);
  6239.       }
  6240.     }
  6241.     if (--this._responseCount == 0 && this._listener) {
  6242.       try {
  6243.         this._listener.onUpdateEnded();
  6244.       }
  6245.       catch (e) {
  6246.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onUpdateEnded: " + e);
  6247.       }
  6248.     }
  6249.   },
  6250. };
  6251.  
  6252. /**
  6253.  * Replaces %...% strings in an addon url (update and updateInfo) with
  6254.  * appropriate values.
  6255.  * @param   aItem
  6256.  *          The nsIUpdateItem representing the item
  6257.  * @param   aURI
  6258.  *          The uri to escape
  6259.  * @param   aDS
  6260.  *          The extensions datasource
  6261.  *
  6262.  * @returns the appropriately escaped uri.
  6263.  */
  6264. function escapeAddonURI(aItem, aAppVersion, aURI, aDS)
  6265. {
  6266.   var itemStatus = "userEnabled";
  6267.   if (aDS.getItemProperty(aItem.id, "userDisabled") == "true" ||
  6268.       aDS.getItemProperty(aItem.id, "userDisabled") == OP_NEEDS_ENABLE)
  6269.     itemStatus = "userDisabled";
  6270.   else if (aDS.getItemProperty(aItem.id, "type") == Ci.nsIUpdateItem.TYPE_THEME) {
  6271.     var currentSkin = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
  6272.     if (aDS.getItemProperty(aItem.id, "internalName") != currentSkin)
  6273.       itemStatus = "userDisabled";
  6274.   }
  6275.  
  6276.   if (aDS.getItemProperty(aItem.id, "compatible") == "false")
  6277.     itemStatus += ",incompatible";
  6278.   if (aDS.getItemProperty(aItem.id, "blocklisted") == "true")
  6279.     itemStatus += ",blocklisted";
  6280.   if (aDS.getItemProperty(aItem.id, "satisfiesDependencies") == "false")
  6281.     itemStatus += ",needsDependencies";
  6282.  
  6283.   aURI = aURI.replace(/%ITEM_ID%/g, aItem.id);
  6284.   aURI = aURI.replace(/%ITEM_VERSION%/g, aItem.version);
  6285.   aURI = aURI.replace(/%ITEM_MAXAPPVERSION%/g, aItem.maxAppVersion);
  6286.   aURI = aURI.replace(/%ITEM_STATUS%/g, itemStatus);
  6287.   aURI = aURI.replace(/%APP_ID%/g, gApp.ID);
  6288.   aURI = aURI.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion : gApp.version);
  6289.   aURI = aURI.replace(/%REQ_VERSION%/g, REQ_VERSION);
  6290.   aURI = aURI.replace(/%APP_OS%/g, gOSTarget);
  6291.   aURI = aURI.replace(/%APP_ABI%/g, gXPCOMABI);
  6292.   aURI = aURI.replace(/%APP_LOCALE%/g, gLocale);
  6293.   aURI = aURI.replace(/%CURRENT_APP_VERSION%/g, gApp.version);
  6294.  
  6295.   // Replace custom parameters (names of custom parameters must have at
  6296.   // least 3 characters to prevent lookups for something like %D0%C8)
  6297.   var catMan = null;
  6298.   aURI = aURI.replace(/%(\w{3,})%/g, function(match, param) {
  6299.     if (!catMan) {
  6300.       catMan = Cc["@mozilla.org/categorymanager;1"].
  6301.                getService(Ci.nsICategoryManager);
  6302.     }
  6303.  
  6304.     try {
  6305.       var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, param);
  6306.       var paramHandler = Cc[contractID].
  6307.                          getService(Ci.nsIPropertyBag2);
  6308.       return paramHandler.getPropertyAsAString(param);
  6309.     }
  6310.     catch(e) {
  6311.       return match;
  6312.     }
  6313.   });
  6314.  
  6315.   // escape() does not properly encode + symbols in any embedded FVF strings.
  6316.   return aURI.replace(/\+/g, "%2B");
  6317. }
  6318.  
  6319. function RDFItemUpdater(aUpdater) {
  6320.   this._updater = aUpdater;
  6321. }
  6322.  
  6323. RDFItemUpdater.prototype = {
  6324.   _updater            : null,
  6325.   _updateCheckType    : 0,
  6326.   _item               : null,
  6327.  
  6328.   checkForUpdates: function RDFItemUpdater_checkForUpdates(aItem, aUpdateCheckType) {
  6329.     // A preference setting can disable updating for this item
  6330.     try {
  6331.       if (!gPref.getBoolPref(PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, aItem.id))) {
  6332.         var status = Ci.nsIAddonUpdateCheckListener.STATUS_DISABLED;
  6333.         this._updater.checkForDone(aItem, status);
  6334.         return;
  6335.       }
  6336.     }
  6337.     catch (e) { }
  6338.  
  6339.     // Items managed by the app are not checked for updates.
  6340.     var emDS = this._updater._emDS;
  6341.     if (emDS.getItemProperty(aItem.id, "appManaged") == "true") {
  6342.       var status = Ci.nsIAddonUpdateCheckListener.STATUS_APP_MANAGED;
  6343.       this._updater.checkForDone(aItem, status);
  6344.       return;
  6345.     }
  6346.  
  6347.     // Items that have a pending install, uninstall, or upgrade are not checked
  6348.     // for updates.
  6349.     var opType = emDS.getItemProperty(aItem.id, "opType");
  6350.     if (opType) {
  6351.       var status = Ci.nsIAddonUpdateCheckListener.STATUS_PENDING_OP;
  6352.       this._updater.checkForDone(aItem, status);
  6353.       return;
  6354.     }
  6355.  
  6356.     var installLocation = InstallLocations.get(emDS.getInstallLocationKey(aItem.id));
  6357.     // Don't check items for updates that are managed independently
  6358.     if (installLocation && installLocation.itemIsManagedIndependently(aItem.id)) {
  6359.       var status = Ci.nsIAddonUpdateCheckListener.STATUS_NOT_MANAGED;
  6360.       this._updater.checkForDone(aItem, status);
  6361.       return;
  6362.     }
  6363.  
  6364.     // Don't check items for updates if the location can't be written to except
  6365.     // when performing a version only update.
  6366.     if ((aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION ||
  6367.          aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) &&
  6368.         (!installLocation || !installLocation.canAccess)) {
  6369.       var status = Ci.nsIAddonUpdateCheckListener.STATUS_READ_ONLY;
  6370.       this._updater.checkForDone(aItem, status);
  6371.       return;
  6372.     }
  6373.  
  6374.     this._updateCheckType = aUpdateCheckType;
  6375.     this._item = aItem;
  6376.  
  6377.     // Look for a custom update URI: 1) supplied by a pref, 2) supplied by the
  6378.     // install manifest, 3) the default configuration
  6379.     try {
  6380.       var dsURI = gPref.getComplexValue(PREF_EM_ITEM_UPDATE_URL.replace(/%UUID%/, aItem.id),
  6381.                                         Ci.nsIPrefLocalizedString).data;
  6382.     }
  6383.     catch (e) { }
  6384.     if (!dsURI)
  6385.       dsURI = aItem.updateRDF;
  6386.     if (!dsURI)
  6387.       dsURI = gPref.getCharPref(PREF_UPDATE_DEFAULT_URL);
  6388.  
  6389.     dsURI = escapeAddonURI(aItem, this._updater._appVersion, dsURI, emDS);
  6390.  
  6391.     // Verify that the URI provided is valid
  6392.     try {
  6393.       var uri = newURI(dsURI);
  6394.     }
  6395.     catch (e) {
  6396.       LOG("RDFItemUpdater:checkForUpdates: There was an error loading the \r\n" +
  6397.           " update datasource for: " + dsURI + ", item = " + aItem.id + ", error: " + e);
  6398.       this._updater.checkForDone(aItem,
  6399.                                  Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6400.       return;
  6401.     }
  6402.  
  6403.     LOG("RDFItemUpdater:checkForUpdates sending a request to server for: " +
  6404.         uri.spec + ", item = " + aItem.objectSource);
  6405.  
  6406.     var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
  6407.                   createInstance(Ci.nsIXMLHttpRequest);
  6408.     request.open("GET", uri.spec, true);
  6409.     request.channel.notificationCallbacks = new BadCertHandler();
  6410.     request.overrideMimeType("text/xml");
  6411.     request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
  6412.  
  6413.     var self = this;
  6414.     request.onerror     = function(event) { self.onXMLError(event, aItem);    };
  6415.     request.onload      = function(event) { self.onXMLLoad(event, aItem);     };
  6416.     request.send(null);
  6417.   },
  6418.  
  6419.   onXMLLoad: function RDFItemUpdater_onXMLLoad(aEvent, aItem) {
  6420.     var request = aEvent.target;
  6421.     try {
  6422.       checkCert(request.channel);
  6423.     }
  6424.     catch (e) {
  6425.       // This may be overly restrictive in two cases: corporate installations
  6426.       // with a corporate update server using an in-house CA cert (installed
  6427.       // but not "built-in") and lone developers hosting their updates on a
  6428.       // site with a self-signed cert (permanently accepted, otherwise the
  6429.       // BadCertHandler would prevent getting this far). Update checks will
  6430.       // fail in both these scenarios.
  6431.       // How else can we protect the vast majority of updates served from AMO
  6432.       // from the spoofing attack described in bug 340198 while allowing those
  6433.       // other cases? A "hackme" pref? Domain-control certs are cheap, getting
  6434.       // one should not be a barrier in either case.
  6435.       LOG("RDFItemUpdater::onXMLLoad: " + e);
  6436.       this._updater.checkForDone(aItem,
  6437.                                  Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6438.       return;
  6439.     }
  6440.     var responseXML = request.responseXML;
  6441.  
  6442.     // If the item does not have an update RDF and returns an error it is not
  6443.     // treated as a failure since all items without an updateURL are checked
  6444.     // for updates on AMO even if they are not hosted there.
  6445.     if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
  6446.         (request.status != 200 && request.status != 0)) {
  6447.       this._updater.checkForDone(aItem, (aItem.updateRDF ? Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE :
  6448.                                                            Ci.nsIAddonUpdateCheckListener.STATUS_NONE));
  6449.       return;
  6450.     }
  6451.  
  6452.     var rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
  6453.                     createInstance(Ci.nsIRDFXMLParser)
  6454.     var ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
  6455.              createInstance(Ci.nsIRDFDataSource);
  6456.     rdfParser.parseString(ds, request.channel.URI, request.responseText);
  6457.  
  6458.     this.onDatasourceLoaded(ds, aItem);
  6459.   },
  6460.  
  6461.   onXMLError: function RDFItemUpdater_onXMLError(aEvent, aItem) {
  6462.     try {
  6463.       var request = aEvent.target;
  6464.       // the following may throw (e.g. a local file or timeout)
  6465.       var status = request.status;
  6466.     }
  6467.     catch (e) {
  6468.       request = aEvent.target.channel.QueryInterface(Ci.nsIRequest);
  6469.       status = request.status;
  6470.     }
  6471.     // this can fail when a network connection is not present.
  6472.     try {
  6473.       var statusText = request.statusText;
  6474.     }
  6475.     catch (e) {
  6476.       status = 0;
  6477.     }
  6478.     // When status is 0 we don't have a valid channel.
  6479.     if (status == 0)
  6480.       statusText = "nsIXMLHttpRequest channel unavailable";
  6481.  
  6482.     LOG("RDFItemUpdater:onError: There was an error loading the \r\n" +
  6483.         "the update datasource for item " + aItem.id + ", error: " + statusText);
  6484.     this._updater.checkForDone(aItem,
  6485.                                Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6486.   },
  6487.  
  6488.   onDatasourceLoaded: function RDFItemUpdater_onDatasourceLoaded(aDatasource, aLocalItem) {
  6489.     /*
  6490. //@line 6689 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  6491.     */
  6492.     if (!aDatasource.GetAllResources().hasMoreElements()) {
  6493.       LOG("RDFItemUpdater:onDatasourceLoaded: Datasource empty.\r\n" +
  6494.           "If you are an Extension developer and were expecting there to be\r\n" +
  6495.           "updates, this could mean any number of things, since the RDF system\r\n" +
  6496.           "doesn't give up much in the way of information when the load fails.\r\n" +
  6497.           "\r\nTry checking that: \r\n" +
  6498.           " 1. Your remote RDF file exists at the location.\r\n" +
  6499.           " 2. Your RDF file is valid XML (starts with <?xml version=\"1.0\"?>\r\n" +
  6500.           "    and loads in Firefox displaying pretty printed like other XML documents\r\n" +
  6501.           " 3. Your server is sending the data in the correct MIME\r\n" +
  6502.           "    type (text/xml)");
  6503.     }      
  6504.  
  6505.     // If we have an update key then the update manifest must be signed
  6506.     if (aLocalItem.updateKey) {
  6507.       var extensionRes = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id);
  6508.       LOG(extensionRes.Value);
  6509.       var signature = this._getPropertyFromResource(aDatasource, extensionRes, "signature", null);
  6510.       if (signature) {
  6511.         var serializer = new RDFSerializer();
  6512.         try {
  6513.           var updateString = serializer.serializeResource(aDatasource, extensionRes);
  6514.           var verifier = Cc["@mozilla.org/security/datasignatureverifier;1"].
  6515.                          getService(Ci.nsIDataSignatureVerifier);
  6516.           try {
  6517.             if (!verifier.verifyData(updateString, signature, aLocalItem.updateKey)) {
  6518.               LOG("RDFItemUpdater:onDatasourceLoaded: Update manifest for " +
  6519.                   aLocalItem.id + " failed signature check.");
  6520.               this._updater.checkForDone(aLocalItem, Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6521.               return;
  6522.             }
  6523.           }
  6524.           catch (e) {
  6525.             LOG("RDFItemUpdater:onDatasourceLoaded: Failed to verify signature for " +
  6526.                 aLocalItem.id + ". This indicates a malformed update key or signature.");
  6527.             this._updater.checkForDone(aLocalItem, Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6528.             return;
  6529.           }
  6530.         }
  6531.         catch (e) {
  6532.           LOG("RDFItemUpdater:onDatasourceLoaded: Failed to generate signature " +
  6533.               "string for " + aLocalItem.id + ". Serializer threw " + e);
  6534.           this._updater.checkForDone(aLocalItem, Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6535.           return;
  6536.         }
  6537.       }
  6538.       else {
  6539.         LOG("RDFItemUpdater:onDatasourceLoaded: Update manifest for " +
  6540.             aLocalItem.id + " did not contain a signature.");
  6541.         this._updater.checkForDone(aLocalItem, Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6542.         return;
  6543.       }
  6544.     }
  6545.     /* If there is no updateKey either the update was over SSL, or it is an old
  6546.      * addon that we are allowing a grace update. */
  6547.  
  6548.     // Parse the response RDF
  6549.     var newerItem, sameItem;
  6550.  
  6551.     // Firefox 1.0PR+ update.rdf format
  6552.     if (this._updateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION ||
  6553.         this._updateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) {
  6554.       // Look for newer versions of this item, we only do this in "normal"
  6555.       // mode... see comment by ExtensionItemUpdater_checkForUpdates
  6556.       // about how we do this in all cases but Install Phone Home - which
  6557.       // only needs to do a version check.
  6558.       newerItem = this._parseV20UpdateInfo(aDatasource, aLocalItem,
  6559.                                            this._updateCheckType);
  6560.  
  6561.       if (newerItem) {
  6562.         ++this._updater._updateCount;
  6563.         LOG("RDFItemUpdater:onDatasourceLoaded: Found a newer version of this item:\r\n" +
  6564.             newerItem.objectSource);
  6565.       }
  6566.     }
  6567.  
  6568.     // Now look for updated version compatibility metadata for the currently
  6569.     // installed version...
  6570.     sameItem = this._parseV20UpdateInfo(aDatasource, aLocalItem,
  6571.                                         Ci.nsIExtensionManager.UPDATE_CHECK_COMPATIBILITY);
  6572.  
  6573.     if (sameItem) {
  6574.       // Install-time updates are not written to the DS because there is no
  6575.       // entry yet, EM just uses the notifications to ascertain (by hand)
  6576.       // whether or not there is a remote maxVersion tweak that makes the
  6577.       // item being installed compatible.
  6578.       if (!this._updater._applyVersionUpdates(aLocalItem, sameItem))
  6579.         sameItem = null;
  6580.       else
  6581.         LOG("RDFItemUpdater:onDatasourceLoaded: Found info about the installed\r\n" +
  6582.             "version of this item: " + sameItem.objectSource);
  6583.     }
  6584.     var item = null, status = Ci.nsIAddonUpdateCheckListener.STATUS_NONE;
  6585.     if ((this._updateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION ||
  6586.          this._updateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION)
  6587.         && newerItem) {
  6588.       item = newerItem;
  6589.       status = Ci.nsIAddonUpdateCheckListener.STATUS_UPDATE;
  6590.     }
  6591.     else if (sameItem) {
  6592.       item = sameItem;
  6593.       status = Ci.nsIAddonUpdateCheckListener.STATUS_VERSIONINFO;
  6594.     }
  6595.     else {
  6596.       item = aLocalItem;
  6597.       status = Ci.nsIAddonUpdateCheckListener.STATUS_NO_UPDATE;
  6598.     }
  6599.     // Only one call of this._updater.checkForDone is needed for RDF
  6600.     // responses, since there is only one response per item.
  6601.     this._updater.checkForDone(item, status);
  6602.   },
  6603.  
  6604.   // Get a compulsory property from a resource. Reports an error if the
  6605.   // property was not present.
  6606.   _getPropertyFromResource: function RDFItemUpdater__getPropertyFromResource(aDataSource,
  6607.                                                                              aSourceResource,
  6608.                                                                              aProperty,
  6609.                                                                              aLocalItem) {
  6610.     var rv;
  6611.     try {
  6612.       var property = gRDF.GetResource(EM_NS(aProperty));
  6613.       rv = stringData(aDataSource.GetTarget(aSourceResource, property, true));
  6614.       if (rv === undefined)
  6615.         throw Cr.NS_ERROR_FAILURE;
  6616.     }
  6617.     catch (e) {
  6618.       // XXXben show console message "aProperty" not found on aSourceResource.
  6619.       return null;
  6620.     }
  6621.     return rv;
  6622.   },
  6623.  
  6624.   /**
  6625.    * Parses the Firefox 1.0RC1+ update manifest format looking for new versions
  6626.    * of updated compatibility information about the given add-on.
  6627.    * @param   aDataSource
  6628.    *          The update manifest's datasource
  6629.    * @param   aLocalItem
  6630.    *          The nsIUpdateItem representing the add-on being checked for updates.
  6631.    * @param   aUpdateCheckType
  6632.    *          The type of update check being performed. See the constants in
  6633.    *          nsIExtensionManager
  6634.    * @returns An nsIUpdateItem holding the update's information if a valid
  6635.    *          update is found or null if not.
  6636.    */
  6637.   _parseV20UpdateInfo: function RDFItemUpdater__parseV20UpdateInfo(aDataSource,
  6638.                                                                    aLocalItem,
  6639.                                                                    aUpdateCheckType) {
  6640.     var extensionRes  = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id);
  6641.  
  6642.     var updatesArc = gRDF.GetResource(EM_NS("updates"));
  6643.     var updates = aDataSource.GetTarget(extensionRes, updatesArc, true);
  6644.  
  6645.     try {
  6646.       updates = updates.QueryInterface(Ci.nsIRDFResource);
  6647.     }
  6648.     catch (e) {
  6649.       LOG("RDFItemUpdater:_parseV20UpdateInfo: No updates were found for:\r\n" +
  6650.           aLocalItem.id + "\r\n" +
  6651.           "If you are an Extension developer and were expecting there to be\r\n" +
  6652.           "updates, this could mean any number of things, since the RDF system\r\n" +
  6653.           "doesn't give up much in the way of information when the load fails.\r\n" +
  6654.           "\r\nTry checking that: \r\n" +
  6655.           " 1. Your RDF File is correct - e.g. check that there is a top level\r\n" +
  6656.           "    RDF Resource with a URI urn:mozilla:extension:{GUID}, and that\r\n" +
  6657.           "    the <em:updates> listed all have matching GUIDs.");
  6658.       return null;
  6659.     }
  6660.  
  6661.     // Track the newest update found
  6662.     var updatedItem = null;
  6663.  
  6664.     var cu = Cc["@mozilla.org/rdf/container-utils;1"].
  6665.              getService(Ci.nsIRDFContainerUtils);
  6666.     if (cu.IsContainer(aDataSource, updates)) {
  6667.       var ctr = getContainer(aDataSource, updates);
  6668.  
  6669.       var versions = ctr.GetElements();
  6670.       while (versions.hasMoreElements()) {
  6671.         // There are two different methodologies for collecting version
  6672.         // information depending on whether or not we've been invoked in
  6673.         // "version updates only" mode or "version+newest" mode.
  6674.         var version = versions.getNext().QueryInterface(Ci.nsIRDFResource);
  6675.         var foundItem = this._parseV20Update(aDataSource, version, aLocalItem,
  6676.                                              updatedItem ? updatedItem.version : aLocalItem.version,
  6677.                                              aUpdateCheckType);
  6678.         if (foundItem) {
  6679.           // When not checking for new versions we can bail out on the first
  6680.           // result.
  6681.           if (aUpdateCheckType)
  6682.             return foundItem;
  6683.           updatedItem = foundItem;
  6684.         }
  6685.       }
  6686.     }
  6687.     return updatedItem;
  6688.   },
  6689.  
  6690.   /**
  6691.    * Parses a single version's update entry looking for the best matching
  6692.    * targetApplication entry.
  6693.    * @param   aDataSource
  6694.    *          The update manifest's datasource
  6695.    * @param   aUpdateResource
  6696.    *          The nsIRDFResource of the update entry.
  6697.    * @param   aLocalItem
  6698.    *          The nsIUpdateItem representing the add-on being checked for updates.
  6699.    * @param   aNewestVersionFound
  6700.    *          When checking for new versions holds the newest version of this
  6701.    *          add-on that we know about. Otherwise holds the current version.
  6702.    * @param   aUpdateCheckType
  6703.    *          The type of update check being performed. See the constants in
  6704.    *          nsIExtensionManager
  6705.    * @returns An nsIUpdateItem holding the update's information if a valid
  6706.    *          update is found or null if not.
  6707.    */
  6708.   _parseV20Update: function RDFItemUpdater__parseV20Update(aDataSource,
  6709.                                                            aUpdateResource,
  6710.                                                            aLocalItem,
  6711.                                                            aNewestVersionFound,
  6712.                                                            aUpdateCheckType) {
  6713.     var version = this._getPropertyFromResource(aDataSource, aUpdateResource,
  6714.                                                 "version", aLocalItem);
  6715.     /* If we are looking for new versions then test whether this discovered
  6716.      * version is greater than any previously found update. Otherwise check
  6717.      * if this update is for the same version as we have installed. */
  6718.     var result = gVersionChecker.compare(version, aNewestVersionFound);
  6719.     if ((aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION ||
  6720.          aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) ? result <= 0 : result != 0)
  6721.       return null;
  6722.  
  6723.     var taArc = gRDF.GetResource(EM_NS("targetApplication"));
  6724.     var targetApps = aDataSource.GetTargets(aUpdateResource, taArc, true);
  6725.     
  6726.     // Track the best update we have found so far
  6727.     var newestUpdateItem = null;
  6728.     while (targetApps.hasMoreElements()) {
  6729.       var targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource);
  6730.       var appID = this._getPropertyFromResource(aDataSource, targetApp, "id", aLocalItem);
  6731.       if (appID != gApp.ID && appID != TOOLKIT_ID)
  6732.         continue;
  6733.  
  6734.       var updateLink = this._getPropertyFromResource(aDataSource, targetApp, "updateLink", aLocalItem);
  6735.       var updateHash = this._getPropertyFromResource(aDataSource, targetApp, "updateHash", aLocalItem);
  6736.       if (aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION ||
  6737.           aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) {
  6738.         // New version information is useless without a link to get it from
  6739.         if (!updateLink)
  6740.           continue;
  6741.  
  6742.         /* If the update link is non-ssl and we do not have a hash or the hash
  6743.          * is of an insecure nature then we must ignore this update. Bypass
  6744.          * this if not checking update security. Currently we only consider
  6745.          * the sha hashing algorithms as secure. */
  6746.         if (gCheckUpdateSecurity && updateLink.substring(0, 6) != "https:" && 
  6747.             (!updateHash || updateHash.substring(0, 3) != "sha")) {
  6748.           LOG("RDFItemUpdater:_parseV20Update: Update for " + aLocalItem.id +
  6749.               " at " + updateLink + " ignored because it is insecure. updateLink " +
  6750.               " must be a https url or an updateHash must be specified.");
  6751.           continue;
  6752.         }
  6753.       }
  6754.  
  6755.       var updatedItem = makeItem(aLocalItem.id,
  6756.                                  version,
  6757.                                  aLocalItem.installLocationKey,
  6758.                                  this._getPropertyFromResource(aDataSource, targetApp, "minVersion", aLocalItem),
  6759.                                  this._getPropertyFromResource(aDataSource, targetApp, "maxVersion", aLocalItem),
  6760.                                  aLocalItem.name,
  6761.                                  updateLink,
  6762.                                  updateHash,
  6763.                                  "", /* Icon URL */
  6764.                                  "", /* RDF Update URL */
  6765.                                  "", /* Update Key */
  6766.                                  aLocalItem.type,
  6767.                                  appID);
  6768.  
  6769.       if (this._updater._isValidUpdate(aLocalItem, updatedItem, aUpdateCheckType)) {
  6770.         if (aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION) {
  6771.           var infourl = this._getPropertyFromResource(aDataSource, targetApp,
  6772.                                                       "updateInfoURL");
  6773.           if (infourl)
  6774.             infourl = EM_L(infourl);
  6775.           this._updater._emDS.setItemProperty(aLocalItem.id,
  6776.                                               EM_R("availableUpdateInfo"),
  6777.                                               infourl);
  6778.         }
  6779.         if (appID == gApp.ID) {
  6780.           // App takes precedence over toolkit.  If we found the app, bail out.
  6781.           return updatedItem;
  6782.         }
  6783.         newestUpdateItem = updatedItem;
  6784.       }
  6785.     }
  6786.     return newestUpdateItem;
  6787.   }
  6788. };
  6789.  
  6790. /**
  6791.  * A serialisation method for RDF data that produces an identical string
  6792.  * provided that the RDF assertions match.
  6793.  * The serialisation is not complete, only assertions stemming from a given
  6794.  * resource are included, multiple references to the same resource are not
  6795.  * permitted, and the RDF prolog and epilog are not included.
  6796.  * RDF Blob and Date literals are not supported.
  6797.  */
  6798. function RDFSerializer()
  6799. {
  6800.   this.cUtils = Cc["@mozilla.org/rdf/container-utils;1"].
  6801.                 getService(Ci.nsIRDFContainerUtils);
  6802.   this.resources = [];
  6803. }
  6804.  
  6805. RDFSerializer.prototype = {
  6806.   INDENT: "  ",      // The indent used for pretty-printing
  6807.   resources: null,   // Array of the resources that have been found
  6808.   
  6809.   /**
  6810.    * Escapes characters from a string that should not appear in XML.
  6811.    * @param string     The string to be escaped
  6812.    * @returns a string with all characters invalid in XML character data
  6813.    *          converted to entity references.
  6814.    */
  6815.   escapeEntities: function RDFSerializer_escapeEntities(string)
  6816.   {
  6817.     string = string.replace(/&/g, "&");
  6818.     string = string.replace(/</g, "<");
  6819.     string = string.replace(/>/g, ">");
  6820.     string = string.replace(/"/g, """);
  6821.     return string;
  6822.   },
  6823.   
  6824.   /**
  6825.    * Serializes all the elements of an RDF container.
  6826.    * @param ds         The datasource holding the data
  6827.    * @param container  The RDF container to output the child elements of
  6828.    * @param indent     The current level of indent for pretty-printing
  6829.    * @returns a string containing the serialized elements.
  6830.    */
  6831.   serializeContainerItems: function RDFSerializer_serializeContainerItems(ds, container, indent)
  6832.   {
  6833.     var result = "";
  6834.     var items = container.GetElements();
  6835.     while (items.hasMoreElements()) {
  6836.       var item = items.getNext().QueryInterface(Ci.nsIRDFResource);
  6837.       result += indent + "<RDF:li>\n"
  6838.       result += this.serializeResource(ds, item, indent + this.INDENT);
  6839.       result += indent + "</RDF:li>\n"
  6840.     }
  6841.     return result;
  6842.   },
  6843.   
  6844.   /**
  6845.    * Serializes all em:* (see EM_NS) properties of an RDF resource except for
  6846.    * the em:signature property. As this serialization is to be compared against
  6847.    * the manifest signature it cannot contain the em:signature property itself.
  6848.    * @param ds         The datasource holding the data
  6849.    * @param resource   The RDF resource to output the properties of
  6850.    * @param indent     The current level of indent for pretty-printing
  6851.    * @returns a string containing the serialized properties.
  6852.    */
  6853.   serializeResourceProperties: function RDFSerializer_serializeResourceProperties(ds, resource, indent)
  6854.   {
  6855.     var result = "";
  6856.     var items = [];
  6857.     var arcs = ds.ArcLabelsOut(resource);
  6858.     while (arcs.hasMoreElements()) {
  6859.       var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource);
  6860.       if (arc.ValueUTF8.substring(0, PREFIX_NS_EM.length) != PREFIX_NS_EM)
  6861.         continue;
  6862.       var prop = arc.ValueUTF8.substring(PREFIX_NS_EM.length);
  6863.       if (prop == "signature")
  6864.         continue;
  6865.   
  6866.       var targets = ds.GetTargets(resource, arc, true);
  6867.       while (targets.hasMoreElements()) {
  6868.         var target = targets.getNext();
  6869.         if (target instanceof Ci.nsIRDFResource) {
  6870.           var item = indent + "<em:" + prop + ">\n";
  6871.           item += this.serializeResource(ds, target, indent + this.INDENT);
  6872.           item += indent + "</em:" + prop + ">\n";
  6873.           items.push(item);
  6874.         }
  6875.         else if (target instanceof Ci.nsIRDFLiteral) {
  6876.           items.push(indent + "<em:" + prop + ">" + this.escapeEntities(target.Value) + "</em:" + prop + ">\n");
  6877.         }
  6878.         else if (target instanceof Ci.nsIRDFInt) {
  6879.           items.push(indent + "<em:" + prop + " NC:parseType=\"Integer\">" + target.Value + "</em:" + prop + ">\n");
  6880.         }
  6881.         else {
  6882.           throw new Error("Cannot serialize unknown literal type");
  6883.         }
  6884.       }
  6885.     }
  6886.     items.sort();
  6887.     result += items.join("");
  6888.     return result;
  6889.   },
  6890.   
  6891.   /**
  6892.    * Recursively serializes an RDF resource and all resources it links to.
  6893.    * This will only output EM_NS properties and will ignore any em:signature
  6894.    * property.
  6895.    * @param ds         The datasource holding the data
  6896.    * @param resource   The RDF resource to serialize
  6897.    * @param indent     The current level of indent for pretty-printing.
  6898.    *                   Leave undefined for no indent
  6899.    * @returns a string containing the serialized resource.
  6900.    * @throws if the RDF data contains multiple references to the same resource.
  6901.    */
  6902.   serializeResource: function RDFSerializer_serializeResource(ds, resource, indent)
  6903.   {
  6904.     if (this.resources.indexOf(resource) != -1 ) {
  6905.       // We cannot output multiple references to the same resource.
  6906.       throw new Error("Cannot serialize multiple references to "+resource.Value);
  6907.     }
  6908.     if (indent === undefined)
  6909.       indent = "";
  6910.     
  6911.     this.resources.push(resource);
  6912.     var container = null;
  6913.     var type = "Description";
  6914.     if (this.cUtils.IsSeq(ds, resource)) {
  6915.       type = "Seq";
  6916.       container = this.cUtils.MakeSeq(ds, resource);
  6917.     }
  6918.     else if (this.cUtils.IsAlt(ds, resource)) {
  6919.       type = "Alt";
  6920.       container = this.cUtils.MakeAlt(ds, resource);
  6921.     }
  6922.     else if (this.cUtils.IsBag(ds, resource)) {
  6923.       type = "Bag";
  6924.       container = this.cUtils.MakeBag(ds, resource);
  6925.     }
  6926.   
  6927.     var result = indent + "<RDF:" + type;
  6928.     if (!gRDF.IsAnonymousResource(resource))
  6929.       result += " about=\"" + this.escapeEntities(resource.ValueUTF8) + "\"";
  6930.     result += ">\n";
  6931.   
  6932.     if (container)
  6933.       result += this.serializeContainerItems(ds, container, indent + this.INDENT);
  6934.       
  6935.     result += this.serializeResourceProperties(ds, resource, indent + this.INDENT);
  6936.   
  6937.     result += indent + "</RDF:" + type + ">\n";
  6938.     return result;
  6939.   }
  6940. }
  6941.  
  6942. /**
  6943.  * A Datasource that holds Extensions.
  6944.  * - Implements nsIRDFDataSource to drive UI
  6945.  * - Uses a RDF/XML datasource for storage (this is undesirable)
  6946.  *
  6947.  * @constructor
  6948.  */
  6949. function ExtensionsDataSource(em) {
  6950.   this._em = em;
  6951.  
  6952.   this._itemRoot = gRDF.GetResource(RDFURI_ITEM_ROOT);
  6953.   this._defaultTheme = gRDF.GetResource(RDFURI_DEFAULT_THEME);
  6954. }
  6955. ExtensionsDataSource.prototype = {
  6956.   _inner    : null,
  6957.   _em       : null,
  6958.   _itemRoot     : null,
  6959.   _defaultTheme : null,
  6960.  
  6961.   /**
  6962.    * Called during application shutdown to clear any references held.
  6963.    * The ExtensionsDataSource is unusable after calling this.
  6964.    */
  6965.   shutdown: function EMDS_shutdown() {
  6966.     this._inner = null;
  6967.     this._em = null;
  6968.     this._itemRoot = null;
  6969.     this._defaultTheme = null;
  6970.   },
  6971.  
  6972.   /**
  6973.    * Determines if an item's dependencies are satisfied. An item's dependencies
  6974.    * are satisifed when all items specified in the item's em:requires arc are
  6975.    * installed, enabled, and the version is compatible based on the em:requires
  6976.    * minVersion and maxVersion.
  6977.    * @param   id
  6978.    *          The ID of the item
  6979.    * @returns true if the item's dependencies are satisfied.
  6980.    *          false if the item's dependencies are not satisfied.
  6981.    */
  6982.   satisfiesDependencies: function EMDS_satisfiesDependencies(id) {
  6983.     var ds = this._inner;
  6984.     var itemResource = getResourceForID(id);
  6985.     var targets = ds.GetTargets(itemResource, EM_R("requires"), true);
  6986.     if (!targets.hasMoreElements())
  6987.       return true;
  6988.  
  6989.     getVersionChecker();
  6990.     var idRes = EM_R("id");
  6991.     var minVersionRes = EM_R("minVersion");
  6992.     var maxVersionRes = EM_R("maxVersion");
  6993.     while (targets.hasMoreElements()) {
  6994.       var target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
  6995.       var dependencyID = stringData(ds.GetTarget(target, idRes, true));
  6996.       var version = null;
  6997.       version = this.getItemProperty(dependencyID, "version");
  6998.       if (version) {
  6999.         var opType = this.getItemProperty(dependencyID, "opType");
  7000.         if (opType ==  OP_NEEDS_DISABLE || opType == OP_NEEDS_UNINSTALL)
  7001.           return false;
  7002.  
  7003.         if (this.getItemProperty(dependencyID, "userDisabled") == "true" ||
  7004.             this.getItemProperty(dependencyID, "appDisabled") == "true" ||
  7005.             this.getItemProperty(dependencyID, "userDisabled") == OP_NEEDS_DISABLE ||
  7006.             this.getItemProperty(dependencyID, "appDisabled") == OP_NEEDS_DISABLE)
  7007.           return false;
  7008.  
  7009.         var minVersion = stringData(ds.GetTarget(target, minVersionRes, true));
  7010.         var maxVersion = stringData(ds.GetTarget(target, maxVersionRes, true));
  7011.         var compatible = (gVersionChecker.compare(version, minVersion) >= 0 &&
  7012.                           gVersionChecker.compare(version, maxVersion) <= 0);
  7013.         if (!compatible)
  7014.           return false;
  7015.       }
  7016.       else {
  7017.         return false;
  7018.       }
  7019.     }
  7020.  
  7021.     return true;
  7022.   },
  7023.  
  7024.   /**
  7025.    * Determine if an item is compatible
  7026.    * @param   datasource
  7027.    *          The datasource to inspect for compatibility - can be the main
  7028.    *          datasource or an Install Manifest.
  7029.    * @param   source
  7030.    *          The RDF Resource of the item to inspect for compatibility.
  7031.    * @param   appVersion
  7032.    *          The version of the application we are checking for compatibility
  7033.    *          against. If this parameter is undefined, the version of the running
  7034.    *          application is used.
  7035.    * @param   platformVersion
  7036.    *          The version of the toolkit to check compatibility against
  7037.    * @returns true if the item is compatible with this version of the
  7038.    *          application, false, otherwise.
  7039.    */
  7040.   isCompatible: function EMDS_isCompatible(datasource, source, appVersion, platformVersion) {
  7041.     // The Default Theme is always compatible.
  7042.     if (source.EqualsNode(this._defaultTheme))
  7043.       return true;
  7044.  
  7045.     var appID = gApp.ID;
  7046.     if (appVersion === undefined)
  7047.       appVersion = gApp.version;
  7048.     if (platformVersion === undefined)
  7049.       var platformVersion = gApp.platformVersion;
  7050.  
  7051.     var targets = datasource.GetTargets(source, EM_R("targetApplication"), true);
  7052.     var idRes = EM_R("id");
  7053.     var minVersionRes = EM_R("minVersion");
  7054.     var maxVersionRes = EM_R("maxVersion");
  7055.     var versionChecker = getVersionChecker();
  7056.     var rv = false;
  7057.     while (targets.hasMoreElements()) {
  7058.       var targetApp = targets.getNext().QueryInterface(Ci.nsIRDFResource);
  7059.       var id          = stringData(datasource.GetTarget(targetApp, idRes, true));
  7060.       var minVersion  = stringData(datasource.GetTarget(targetApp, minVersionRes, true));
  7061.       var maxVersion  = stringData(datasource.GetTarget(targetApp, maxVersionRes, true));
  7062.       if (id == appID) {
  7063.         rv = (versionChecker.compare(appVersion, minVersion) >= 0) &&
  7064.              (versionChecker.compare(appVersion, maxVersion) <= 0);
  7065.         return rv; // App takes precedence over toolkit.
  7066.       }
  7067.  
  7068.       if (id == TOOLKIT_ID) {
  7069.         rv =  (versionChecker.compare(platformVersion, minVersion) >= 0) &&
  7070.               (versionChecker.compare(platformVersion, maxVersion) <= 0);
  7071.         // Keep looping, in case the app id is later.
  7072.       }
  7073.     }
  7074.     return rv;
  7075.   },
  7076.  
  7077.   /**
  7078.    * Gets a list of items that are incompatible with a specific application version.
  7079.    * @param   appID
  7080.    *          The ID of the application - XXXben unused?
  7081.    * @param   appVersion
  7082.    *          The Version of the application to check for incompatibility against.
  7083.    * @param   platformVersion
  7084.    *          The version of the toolkit to check compatibility against
  7085.    * @param   desiredType
  7086.    *          The nsIUpdateItem type of items to look for
  7087.    * @param   includeDisabled
  7088.    *          Whether or not disabled items should be included in the set returned
  7089.    * @returns An array of nsIUpdateItems that are incompatible with the application
  7090.    *          ID/Version supplied.
  7091.    */
  7092.   getIncompatibleItemList: function EMDS_getIncompatibleItemList(appID,
  7093.                                                                  appVersion,
  7094.                                                                  platformVersion,
  7095.                                                                  desiredType,
  7096.                                                                  includeDisabled) {
  7097.     var items = [];
  7098.     var ctr = getContainer(this._inner, this._itemRoot);
  7099.     var elements = ctr.GetElements();
  7100.     while (elements.hasMoreElements()) {
  7101.       var item = elements.getNext().QueryInterface(Ci.nsIRDFResource);
  7102.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  7103.       var type = this.getItemProperty(id, "type");
  7104.       // Skip this item if we're not seeking disabled items
  7105.       if (!includeDisabled && this.getItemProperty(id, "isDisabled") == "true")
  7106.         continue;
  7107.  
  7108.       // If the id of this item matches one of the items potentially installed
  7109.       // with and maintained by this application AND it is installed in the
  7110.       // global install location (i.e. the place installed by the app installer)
  7111.       // it is and can be managed by the update file - it's not an item that has
  7112.       // been manually installed by the user into their profile dir, and as such
  7113.       // it is always compatible with the next release of the application since
  7114.       // we will continue to support it.
  7115.       var locationKey = this.getItemProperty(id, "installLocation");
  7116.       var appManaged = this.getItemProperty(id, "appManaged") == "true";
  7117.       if (appManaged && (locationKey == KEY_APP_GLOBAL || locationKey == KEY_GRE_GLOBAL))
  7118.         continue;
  7119.  
  7120.       if (type != -1 && (type & desiredType) &&
  7121.           !this.isCompatible(this, item, appVersion, platformVersion))
  7122.         items.push(this.getItemForID(id));
  7123.     }
  7124.     return items;
  7125.   },
  7126.  
  7127.   /**
  7128.    * Gets a list of items of a specific type
  7129.    * @param   desiredType
  7130.    *          The nsIUpdateItem type of items to return
  7131.    * @param   countRef
  7132.    *          The XPCJS reference to the size of the returned array
  7133.    * @returns An array of nsIUpdateItems, populated only with an item for |id|
  7134.    *          if |id| is non-null, otherwise all items matching the specified
  7135.    *          type.
  7136.    */
  7137.   getItemList: function EMDS_getItemList(desiredType, countRef) {
  7138.     var items = [];
  7139.     var ctr = getContainer(this, this._itemRoot);
  7140.     var elements = ctr.GetElements();
  7141.     while (elements.hasMoreElements()) {
  7142.       var e = elements.getNext().QueryInterface(Ci.nsIRDFResource);
  7143.       var eID = stripPrefix(e.Value, PREFIX_ITEM_URI);
  7144.       var type = this.getItemProperty(eID, "type");
  7145.       if (type != -1 && type & desiredType)
  7146.         items.push(this.getItemForID(eID));
  7147.     }
  7148.     countRef.value = items.length;
  7149.     return items;
  7150.   },
  7151.  
  7152.   /**
  7153.    * Retrieves a list of installed nsIUpdateItems of items that are dependent
  7154.    * on another item.
  7155.    * @param   id
  7156.    *          The ID of the item that other items depend on.
  7157.    * @param   includeDisabled
  7158.    *          Whether to include disabled items in the set returned.
  7159.    * @param   countRef
  7160.    *          The XPCJS reference to the number of items returned.
  7161.    * @returns An array of installed nsIUpdateItems that depend on the item
  7162.    *          specified by the id parameter.
  7163.    */
  7164.   getDependentItemListForID: function EMDS_getDependentItemListForID(id,
  7165.                                                                      includeDisabled,
  7166.                                                                      countRef) {
  7167.     var items = [];
  7168.     var ds = this._inner;
  7169.     var ctr = getContainer(this, this._itemRoot);
  7170.     var elements = ctr.GetElements();
  7171.     while (elements.hasMoreElements()) {
  7172.       var e = elements.getNext().QueryInterface(Ci.nsIRDFResource);
  7173.       var dependentID = stripPrefix(e.Value, PREFIX_ITEM_URI);
  7174.       var targets = ds.GetTargets(e, EM_R("requires"), true);
  7175.       var idRes = EM_R("id");
  7176.       while (targets.hasMoreElements()) {
  7177.         var target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
  7178.         var dependencyID = stringData(ds.GetTarget(target, idRes, true));
  7179.         if (dependencyID == id) {
  7180.           if (!includeDisabled && this.getItemProperty(dependentID, "isDisabled") == "true")
  7181.             continue;
  7182.           items.push(this.getItemForID(dependentID));
  7183.           break;
  7184.         }
  7185.       }
  7186.     }
  7187.     countRef.value = items.length;
  7188.     return items;
  7189.   },
  7190.  
  7191.   /**
  7192.    * Constructs an nsIUpdateItem for the given item ID
  7193.    * @param   id
  7194.    *          The GUID of the item to construct a nsIUpdateItem for
  7195.    * @returns The nsIUpdateItem for the id.
  7196.    */
  7197.   getItemForID: function EMDS_getItemForID(id) {
  7198.     if (!this.visibleItems[id])
  7199.       return null;
  7200.  
  7201.     var r = getResourceForID(id);
  7202.     if (!r)
  7203.       return null;
  7204.  
  7205.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  7206.     var updateHash = this.getItemProperty(id, "availableUpdateHash");
  7207.     return makeItem(id,
  7208.                     this.getItemProperty(id, "version"),
  7209.                     this.getItemProperty(id, "installLocation"),
  7210.                     targetAppInfo ? targetAppInfo.minVersion : "",
  7211.                     targetAppInfo ? targetAppInfo.maxVersion : "",
  7212.                     this.getItemProperty(id, "name"),
  7213.                     this.getItemProperty(id, "availableUpdateURL"),
  7214.                     updateHash ? updateHash : "",
  7215.                     this.getItemProperty(id, "iconURL"),
  7216.                     this.getItemProperty(id, "updateURL"),
  7217.                     this.getItemProperty(id, "updateKey"),
  7218.                     this.getItemProperty(id, "type"),
  7219.                     targetAppInfo ? targetAppInfo.appID : gApp.ID);
  7220.   },
  7221.  
  7222.   /**
  7223.    * Gets the name of the Install Location where an item is installed.
  7224.    * @param   id
  7225.    *          The GUID of the item to locate an Install Location for
  7226.    * @returns The string name of the Install Location where the item is
  7227.    *          installed.
  7228.    */
  7229.   getInstallLocationKey: function EMDS_getInstallLocationKey(id) {
  7230.     return this.getItemProperty(id, "installLocation");
  7231.   },
  7232.  
  7233.   /**
  7234.    * Sets an RDF property on an item in a datasource. Does not create
  7235.    * multiple assertions
  7236.    * @param   datasource
  7237.    *          The target datasource where the property should be set
  7238.    * @param   source
  7239.    *          The RDF Resource to set the property on
  7240.    * @param   property
  7241.    *          The RDF Resource of the property to set
  7242.    * @param   newValue
  7243.    *          The RDF Node containing the new property value
  7244.    */
  7245.   _setProperty: function EMDS__setProperty(datasource, source, property, newValue) {
  7246.     var oldValue = datasource.GetTarget(source, property, true);
  7247.     if (oldValue) {
  7248.       if (newValue)
  7249.         datasource.Change(source, property, oldValue, newValue);
  7250.       else
  7251.         datasource.Unassert(source, property, oldValue);
  7252.     }
  7253.     else if (newValue)
  7254.       datasource.Assert(source, property, newValue, true);
  7255.   },
  7256.  
  7257.   /**
  7258.    * Gets the updated target application info if it exists for an item from
  7259.    * the Extensions datasource during an installation or upgrade.
  7260.    * @param   id
  7261.    *          The ID of the item to discover updated target application info for
  7262.    * @returns A JS Object with the following properties:
  7263.    *          "id"            The id of the item
  7264.    *          "minVersion"    The updated minimum version of the target
  7265.    *                          application that this item can run in
  7266.    *          "maxVersion"    The updated maximum version of the target
  7267.    *                          application that this item can run in
  7268.    */
  7269.   getUpdatedTargetAppInfo: function EMDS_getUpdatedTargetAppInfo(id) {
  7270.     // The default theme is always compatible so there is never update info.
  7271.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  7272.       return null;
  7273.  
  7274.     var appID = gApp.ID;
  7275.     var r = getResourceForID(id);
  7276.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  7277.     if (!targetApps.hasMoreElements())
  7278.       targetApps = this._inner.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true);
  7279.     var outData = null;
  7280.     while (targetApps.hasMoreElements()) {
  7281.       var targetApp = targetApps.getNext();
  7282.       if (targetApp instanceof Ci.nsIRDFResource) {
  7283.         try {
  7284.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  7285.           // Different target application?
  7286.           if (foundAppID != appID && foundAppID != TOOLKIT_ID)
  7287.             continue;
  7288.           var updatedMinVersion = this._inner.GetTarget(targetApp, EM_R("updatedMinVersion"), true);
  7289.           var updatedMaxVersion = this._inner.GetTarget(targetApp, EM_R("updatedMaxVersion"), true);
  7290.           if (updatedMinVersion && updatedMaxVersion)
  7291.             outData = { id          : id,
  7292.                         targetAppID : foundAppID,
  7293.                         minVersion  : stringData(updatedMinVersion),
  7294.                         maxVersion  : stringData(updatedMaxVersion) };
  7295.           if (foundAppID == appID)
  7296.             return outData;
  7297.         }
  7298.         catch (e) {
  7299.           continue;
  7300.         }
  7301.       }
  7302.     }
  7303.     return outData;
  7304.   },
  7305.  
  7306.   /**
  7307.    * Sets the updated target application info for an item in the Extensions
  7308.    * datasource during an installation or upgrade.
  7309.    * @param   id
  7310.    *          The ID of the item to set updated target application info for
  7311.    * @param   targetAppID
  7312.    *          The target application ID used for checking compatibility for this item.
  7313.    * @param   updatedMinVersion
  7314.    *          The updated minimum version of the target application that this
  7315.    *          item can run in
  7316.    * @param   updatedMaxVersion
  7317.    *          The updated maximum version of the target application that this
  7318.    *          item can run in
  7319.    *
  7320.    * @note Add-ons can specify a targetApplication id of toolkit@mozilla.org in
  7321.    *       their install manifest for compatibility with all apps using a
  7322.    *       specific release of the toolkit.
  7323.    */
  7324.   setUpdatedTargetAppInfo: function EMDS_setUpdatedTargetAppInfo(id, targetAppID,
  7325.                                                                  updatedMinVersion,
  7326.                                                                  updatedMaxVersion) {
  7327.     // The default theme is always compatible so it is never updated.
  7328.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  7329.       return;
  7330.  
  7331.     // Version/Dependency Info
  7332.     var updatedMinVersionRes = EM_R("updatedMinVersion");
  7333.     var updatedMaxVersionRes = EM_R("updatedMaxVersion");
  7334.  
  7335.     var appID = gApp.ID;
  7336.     var r = getResourceForID(id);
  7337.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  7338.     // add updatedMinVersion and updatedMaxVersion for an install else an upgrade
  7339.     if (!targetApps.hasMoreElements()) {
  7340.       var idRes = EM_R("id");
  7341.       var targetRes = getResourceForID(id);
  7342.       var property = EM_R("targetApplication");
  7343.       var anon = gRDF.GetAnonymousResource();
  7344.       this._inner.Assert(anon, idRes, EM_L(appID), true);
  7345.       this._inner.Assert(anon, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  7346.       this._inner.Assert(anon, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  7347.       this._inner.Assert(targetRes, property, anon, true);
  7348.     }
  7349.     else {
  7350.       while (targetApps.hasMoreElements()) {
  7351.         var targetApp = targetApps.getNext();
  7352.         if (targetApp instanceof Ci.nsIRDFResource) {
  7353.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  7354.           // Different target application?
  7355.           if (foundAppID != targetAppID)
  7356.             continue;
  7357.           this._inner.Assert(targetApp, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  7358.           this._inner.Assert(targetApp, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  7359.           break;
  7360.         }
  7361.       }
  7362.     }
  7363.     this.Flush();
  7364.   },
  7365.  
  7366.   /**
  7367.    * Gets the target application info for an item from a datasource.
  7368.    * @param   id
  7369.    *          The GUID of the item to discover target application info for
  7370.    * @param   datasource
  7371.    *          The datasource to look up target application info in
  7372.    * @returns A JS Object with the following properties:
  7373.    *          "appID"         The target application ID used for checking
  7374.    *                          compatibility for this item.
  7375.    *          "minVersion"    The minimum version of the target application
  7376.    *                          that this item can run in
  7377.    *          "maxVersion"    The maximum version of the target application
  7378.    *                          that this item can run in
  7379.    *          or null, if no target application data exists for the specified
  7380.    *          id in the supplied datasource.
  7381.    */
  7382.   getTargetApplicationInfo: function EMDS_getTargetApplicationInfo(id, datasource) {
  7383.     var appID = gApp.ID;
  7384.     // The default theme is always compatible.
  7385.     if (getResourceForID(id).EqualsNode(this._defaultTheme)) {
  7386.       var ver = gApp.version;
  7387.       return { appID: appID, minVersion: ver, maxVersion: ver };
  7388.     }
  7389.  
  7390.     var r = getResourceForID(id);
  7391.     var targetApps = datasource.GetTargets(r, EM_R("targetApplication"), true);
  7392.     if (!targetApps)
  7393.       return null;
  7394.  
  7395.     if (!targetApps.hasMoreElements())
  7396.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true);
  7397.     var outData = null;
  7398.     while (targetApps.hasMoreElements()) {
  7399.       var targetApp = targetApps.getNext();
  7400.       if (targetApp instanceof Ci.nsIRDFResource) {
  7401.         try {
  7402.           var foundAppID = stringData(datasource.GetTarget(targetApp, EM_R("id"), true));
  7403.           // Different target application?
  7404.           if (foundAppID != appID && foundAppID != TOOLKIT_ID)
  7405.             continue;
  7406.  
  7407.           outData = { appID: foundAppID,
  7408.                       minVersion: stringData(datasource.GetTarget(targetApp, EM_R("minVersion"), true)),
  7409.                       maxVersion: stringData(datasource.GetTarget(targetApp, EM_R("maxVersion"), true)) };
  7410.           if (foundAppID == appID)
  7411.             return outData;
  7412.         }
  7413.         catch (e) {
  7414.           continue;
  7415.         }
  7416.       }
  7417.     }
  7418.     return outData;
  7419.   },
  7420.  
  7421.   /**
  7422.    * Sets the target application info for an item in a datasource.
  7423.    * @param   id
  7424.    *          The GUID of the item to discover target application info for
  7425.    * @param   targetAppID
  7426.    *          The target application ID used for checking compatibility for this
  7427.    *          item.
  7428.    * @param   minVersion
  7429.    *          The minimum version of the target application that this item can
  7430.    *          run in
  7431.    * @param   maxVersion
  7432.    *          The maximum version of the target application that this item can
  7433.    *          run in
  7434.    * @param   datasource
  7435.    *          The datasource to look up target application info in
  7436.    *
  7437.    * @note Add-ons can specify a targetApplication id of toolkit@mozilla.org in
  7438.    *       their install manifest for compatibility with all apps using a
  7439.    *       specific release of the toolkit.
  7440.    */
  7441.   setTargetApplicationInfo: function EMDS_setTargetApplicationInfo(id, targetAppID,
  7442.                                                                    minVersion,
  7443.                                                                    maxVersion,
  7444.                                                                    datasource) {
  7445.     var targetDataSource = datasource;
  7446.     if (!targetDataSource)
  7447.       targetDataSource = this._inner;
  7448.  
  7449.     var appID = gApp.ID;
  7450.     var r = getResourceForID(id);
  7451.     var targetApps = targetDataSource.GetTargets(r, EM_R("targetApplication"), true);
  7452.     if (!targetApps.hasMoreElements())
  7453.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true);
  7454.     while (targetApps.hasMoreElements()) {
  7455.       var targetApp = targetApps.getNext();
  7456.       if (targetApp instanceof Ci.nsIRDFResource) {
  7457.         var foundAppID = stringData(targetDataSource.GetTarget(targetApp, EM_R("id"), true));
  7458.         // Different target application?
  7459.         if (foundAppID != targetAppID)
  7460.           continue;
  7461.  
  7462.         this._setProperty(targetDataSource, targetApp, EM_R("minVersion"), EM_L(minVersion));
  7463.         this._setProperty(targetDataSource, targetApp, EM_R("maxVersion"), EM_L(maxVersion));
  7464.  
  7465.         // If we were setting these properties on the main datasource, flush
  7466.         // it now. (Don't flush changes set on Install Manifests - they are
  7467.         // fleeting).
  7468.         if (!datasource)
  7469.           this.Flush();
  7470.  
  7471.         break;
  7472.       }
  7473.     }
  7474.   },
  7475.  
  7476.   /**
  7477.    * Gets a property of an item
  7478.    * @param   id
  7479.    *          The GUID of the item
  7480.    * @param   property
  7481.    *          The name of the property (excluding EM_NS)
  7482.    * @returns The literal value of the property, or undefined if there is no
  7483.    *          value.
  7484.    */
  7485.   getItemProperty: function EMDS_getItemProperty(id, property) {
  7486.     var item = getResourceForID(id);
  7487.     if (!item) {
  7488.       LOG("getItemProperty failing for lack of an item. This means getResourceForItem \
  7489.            failed to locate a resource for aItemID (item ID = " + id + ", property = " + property + ")");
  7490.     }
  7491.     else
  7492.       return this._getItemProperty(item, property);
  7493.     return undefined;
  7494.   },
  7495.  
  7496.   /**
  7497.    * Gets a property of an item resource
  7498.    * @param   itemResource
  7499.    *          The RDF Resource of the item
  7500.    * @param   property
  7501.    *          The name of the property (excluding EM_NS)
  7502.    * @returns The literal value of the property, or undefined if there is no
  7503.    *          value.
  7504.    */
  7505.   _getItemProperty: function EMDS__getItemProperty(itemResource, property) {
  7506.     var target = this.GetTarget(itemResource, EM_R(property), true);
  7507.     var value = stringData(target);
  7508.     if (value === undefined)
  7509.       value = intData(target);
  7510.     return value === undefined ? "" : value;
  7511.   },
  7512.  
  7513.   /**
  7514.    * Sets a property on an item.
  7515.    * @param   id
  7516.    *          The GUID of the item
  7517.    * @param   propertyArc
  7518.    *          The RDF Resource of the property arc
  7519.    * @param   propertyValue
  7520.    *          A nsIRDFLiteral value of the property to be set
  7521.    */
  7522.   setItemProperty: function EMDS_setItemProperty(id, propertyArc, propertyValue) {
  7523.     var item = getResourceForID(id);
  7524.     this._setProperty(this._inner, item, propertyArc, propertyValue);
  7525.     this.Flush();
  7526.   },
  7527.  
  7528.   /**
  7529.    * Sets one or more properties for an item.
  7530.    * @param   id
  7531.    *          The ID of the item
  7532.    * @param   properties
  7533.    *          A JS object which maps properties to values.
  7534.    */
  7535.   setItemProperties: function EMDS_setItemProperties(id, properties) {
  7536.     var item = getResourceForID(id);
  7537.     for (var key in properties)
  7538.       this._setProperty(this._inner, item, EM_R(key), properties[key]);
  7539.     this.Flush();
  7540.   },
  7541.  
  7542.   /**
  7543.    * Inserts the RDF resource for an item into a container.
  7544.    * @param   id
  7545.    *          The GUID of the item
  7546.    */
  7547.   insertItemIntoContainer: function EMDS_insertItemIntoContainer(id) {
  7548.     // Get the target container and resource
  7549.     var ctr = getContainer(this._inner, this._itemRoot);
  7550.     var itemResource = getResourceForID(id);
  7551.     // Don't bother adding the extension to the list if it's already there.
  7552.     // (i.e. we're upgrading)
  7553.     var oldIndex = ctr.IndexOf(itemResource);
  7554.     if (oldIndex == -1)
  7555.       ctr.AppendElement(itemResource);
  7556.     this.Flush();
  7557.   },
  7558.  
  7559.   /**
  7560.    * Removes the RDF resource for an item from its container.
  7561.    * @param   id
  7562.    *          The GUID of the item
  7563.    */
  7564.   removeItemFromContainer: function EMDS_removeItemFromContainer(id) {
  7565.     var ctr = getContainer(this._inner, this._itemRoot);
  7566.     var itemResource = getResourceForID(id);
  7567.     ctr.RemoveElement(itemResource, true);
  7568.     this.Flush();
  7569.   },
  7570.  
  7571.   /**
  7572.    * Removes a corrupt item entry from the extension list added due to buggy
  7573.    * code in previous EM versions!
  7574.    * @param   id
  7575.    *          The GUID of the item
  7576.    */
  7577.   removeCorruptItem: function EMDS_removeCorruptItem(id) {
  7578.     this.removeItemMetadata(id);
  7579.     this.removeItemFromContainer(id);
  7580.     this.visibleItems[id] = null;
  7581.   },
  7582.  
  7583.   /**
  7584.    * Removes a corrupt download entry from the list
  7585.    * @param   uri
  7586.    *          The RDF URI of the item.
  7587.    * @returns The RDF Resource of the removed entry
  7588.    */
  7589.   removeCorruptDLItem: function EMDS_removeCorruptDLItem(uri) {
  7590.     var itemResource = gRDF.GetResource(uri);
  7591.     var ctr = getContainer(this._inner, this._itemRoot);
  7592.     if (ctr.IndexOf(itemResource) != -1) {
  7593.       ctr.RemoveElement(itemResource, true);
  7594.       this._cleanResource(itemResource);
  7595.       this.Flush();
  7596.     }
  7597.     return itemResource;
  7598.   },
  7599.  
  7600.   /**
  7601.    * Copies localized properties from an install manifest to the datasource
  7602.    *
  7603.    * @param   installManifest
  7604.    *          The Install Manifest datasource we are copying from
  7605.    * @param   source
  7606.    *          The source resource of the localized properties
  7607.    * @param   target
  7608.    *          The target resource to store the localized properties
  7609.    */
  7610.   _addLocalizedMetadata: function EMDS__addLocalizedMetadata(installManifest,
  7611.                                                              sourceRes, targetRes)
  7612.   {
  7613.     var singleProps = ["name", "description", "creator", "homepageURL"];
  7614.  
  7615.     for (var i = 0; i < singleProps.length; ++i) {
  7616.       var property = EM_R(singleProps[i]);
  7617.       var literal = installManifest.GetTarget(sourceRes, property, true);
  7618.       // If literal is null, _setProperty will remove any existing.
  7619.       this._setProperty(this._inner, targetRes, property, literal);
  7620.     }
  7621.  
  7622.     // Assert properties with multiple values
  7623.     var manyProps = ["developer", "translator", "contributor"];
  7624.     for (var i = 0; i < manyProps.length; ++i) {
  7625.       var property = EM_R(manyProps[i]);
  7626.       var literals = installManifest.GetTargets(sourceRes, property, true);
  7627.  
  7628.       var oldValues = this._inner.GetTargets(targetRes, property, true);
  7629.       while (oldValues.hasMoreElements()) {
  7630.         var oldValue = oldValues.getNext().QueryInterface(Ci.nsIRDFNode);
  7631.         this._inner.Unassert(targetRes, property, oldValue);
  7632.       }
  7633.       while (literals.hasMoreElements()) {
  7634.         var literal = literals.getNext().QueryInterface(Ci.nsIRDFNode);
  7635.         this._inner.Assert(targetRes, property, literal, true);
  7636.       }
  7637.     }
  7638.  
  7639.   },
  7640.  
  7641.   /**
  7642.    * Copies metadata from an Install Manifest Datasource into the Extensions
  7643.    * DataSource.
  7644.    * @param   id
  7645.    *          The GUID of the item
  7646.    * @param   installManifest
  7647.    *          The Install Manifest datasource we are copying from
  7648.    * @param   installLocation
  7649.    *          The Install Location of the item.
  7650.    */
  7651.   addItemMetadata: function EMDS_addItemMetadata(id, installManifest, installLocation) {
  7652.     var targetRes = getResourceForID(id);
  7653.     // Remove any temporary assertions used for the install process
  7654.     this._setProperty(this._inner, targetRes, EM_R("newVersion"), null);
  7655.     // Copy the assertions over from the source datasource.
  7656.     // Assert properties with single values
  7657.     var singleProps = ["version", "updateURL", "updateService", "optionsURL",
  7658.                        "aboutURL", "iconURL", "internalName", "updateKey"];
  7659.  
  7660.     // Items installed into restricted Install Locations can also be locked
  7661.     // (can't be removed or disabled), and hidden (not shown in the UI)
  7662.     if (installLocation.restricted)
  7663.       singleProps = singleProps.concat(["locked", "hidden"]);
  7664.     if (installLocation.name == KEY_APP_GLOBAL || installLocation.name == KEY_GRE_GLOBAL)
  7665.       singleProps = singleProps.concat(["appManaged"]);
  7666.     for (var i = 0; i < singleProps.length; ++i) {
  7667.       var property = EM_R(singleProps[i]);
  7668.       var literal = installManifest.GetTarget(gInstallManifestRoot, property, true);
  7669.       // If literal is null, _setProperty will remove any existing.
  7670.       this._setProperty(this._inner, targetRes, property, literal);
  7671.     }
  7672.  
  7673.     var localizedProp = EM_R("localized");
  7674.     var localeProp = EM_R("locale");
  7675.     // Remove old localized properties
  7676.     var oldValues = this._inner.GetTargets(targetRes, localizedProp, true);
  7677.     while (oldValues.hasMoreElements()) {
  7678.       var oldValue = oldValues.getNext().QueryInterface(Ci.nsIRDFNode);
  7679.       this._cleanResource(oldValue);
  7680.       this._inner.Unassert(targetRes, localizedProp, oldValue);
  7681.     }
  7682.     // Add each localized property
  7683.     var localizations = installManifest.GetTargets(gInstallManifestRoot, localizedProp, true);
  7684.     while (localizations.hasMoreElements()) {
  7685.       var localization = localizations.getNext().QueryInterface(Ci.nsIRDFResource);
  7686.       var anon = gRDF.GetAnonymousResource();
  7687.       var literals = installManifest.GetTargets(localization, localeProp, true);
  7688.       while (literals.hasMoreElements()) {
  7689.         var literal = literals.getNext().QueryInterface(Ci.nsIRDFNode);
  7690.         this._inner.Assert(anon, localeProp, literal, true);
  7691.       }
  7692.       this._addLocalizedMetadata(installManifest, localization, anon);
  7693.       this._inner.Assert(targetRes, localizedProp, anon, true);
  7694.     }
  7695.     // Add the fallback properties
  7696.     this._addLocalizedMetadata(installManifest, gInstallManifestRoot, targetRes);
  7697.  
  7698.     // Version/Dependency Info
  7699.     var versionProps = ["targetApplication", "requires"];
  7700.     var idRes = EM_R("id");
  7701.     var minVersionRes = EM_R("minVersion");
  7702.     var maxVersionRes = EM_R("maxVersion");
  7703.     for (var i = 0; i < versionProps.length; ++i) {
  7704.       var property = EM_R(versionProps[i]);
  7705.       var newVersionInfos = installManifest.GetTargets(gInstallManifestRoot, property, true);
  7706.  
  7707.       var oldVersionInfos = this._inner.GetTargets(targetRes, property, true);
  7708.       while (oldVersionInfos.hasMoreElements()) {
  7709.         var oldVersionInfo = oldVersionInfos.getNext().QueryInterface(Ci.nsIRDFResource);
  7710.         this._cleanResource(oldVersionInfo);
  7711.         this._inner.Unassert(targetRes, property, oldVersionInfo);
  7712.       }
  7713.       while (newVersionInfos.hasMoreElements()) {
  7714.         var newVersionInfo = newVersionInfos.getNext().QueryInterface(Ci.nsIRDFResource);
  7715.         var anon = gRDF.GetAnonymousResource();
  7716.         this._inner.Assert(anon, idRes, installManifest.GetTarget(newVersionInfo, idRes, true), true);
  7717.         this._inner.Assert(anon, minVersionRes, installManifest.GetTarget(newVersionInfo, minVersionRes, true), true);
  7718.         this._inner.Assert(anon, maxVersionRes, installManifest.GetTarget(newVersionInfo, maxVersionRes, true), true);
  7719.         this._inner.Assert(targetRes, property, anon, true);
  7720.       }
  7721.     }
  7722.     this.updateProperty(id, "opType");
  7723.     this.updateProperty(id, "updateable");
  7724.     this.Flush();
  7725.   },
  7726.  
  7727.   /**
  7728.    * Strips an item entry of all assertions.
  7729.    * @param   id
  7730.    *          The GUID of the item
  7731.    */
  7732.   removeItemMetadata: function EMDS_removeItemMetadata(id) {
  7733.     var item = getResourceForID(id);
  7734.     var resources = ["targetApplication", "requires", "localized"];
  7735.     for (var i = 0; i < resources.length; ++i) {
  7736.       var targetApps = this._inner.GetTargets(item, EM_R(resources[i]), true);
  7737.       while (targetApps.hasMoreElements()) {
  7738.         var targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource);
  7739.         this._cleanResource(targetApp);
  7740.       }
  7741.     }
  7742.  
  7743.     this._cleanResource(item);
  7744.   },
  7745.  
  7746.   /**
  7747.    * Strips a resource of all outbound assertions. We use methods like this
  7748.    * since the RDFXMLDatasource will write out all assertions, even if they
  7749.    * are not connected through our root.
  7750.    * @param   resource
  7751.    *          The resource to clean.
  7752.    */
  7753.   _cleanResource: function EMDS__cleanResource(resource) {
  7754.     // Remove outward arcs
  7755.     var arcs = this._inner.ArcLabelsOut(resource);
  7756.     while (arcs.hasMoreElements()) {
  7757.       var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource);
  7758.       var targets = this._inner.GetTargets(resource, arc, true);
  7759.       while (targets.hasMoreElements()) {
  7760.         var value = targets.getNext().QueryInterface(Ci.nsIRDFNode);
  7761.         if (value)
  7762.           this._inner.Unassert(resource, arc, value);
  7763.       }
  7764.     }
  7765.   },
  7766.  
  7767.   /**
  7768.    * Notify views that this propery has changed (this is for properties that
  7769.    * are implemented by this datasource rather than by the inner in-memory
  7770.    * datasource and thus do not get free change handling).
  7771.    * @param   id
  7772.    *          The GUID of the item to update the property for.
  7773.    * @param   property
  7774.    *          The property (less EM_NS) to update.
  7775.    */
  7776.   updateProperty: function EMDS_updateProperty(id, property) {
  7777.     var item = getResourceForID(id);
  7778.     this._updateProperty(item, property);
  7779.   },
  7780.  
  7781.   /**
  7782.    * Notify views that this propery has changed (this is for properties that
  7783.    * are implemented by this datasource rather than by the inner in-memory
  7784.    * datasource and thus do not get free change handling). This allows updating
  7785.    * properties for download items which don't have the em item prefix in there
  7786.    ( resource value. In most instances updateProperty should be used.
  7787.    * @param   item
  7788.    *          The item to update the property for.
  7789.    * @param   property
  7790.    *          The property (less EM_NS) to update.
  7791.    */
  7792.   _updateProperty: function EMDS__updateProperty(item, property) {
  7793.     if (item) {
  7794.       var propertyResource = EM_R(property);
  7795.       var value = this.GetTarget(item, propertyResource, true);
  7796.       for (var i = 0; i < this._observers.length; ++i) {
  7797.         if (value)
  7798.           this._observers[i].onChange(this, item, propertyResource,
  7799.                                       EM_L(""), value);
  7800.         else
  7801.           this._observers[i].onUnassert(this, item, propertyResource,
  7802.                                         EM_L(""));
  7803.       }
  7804.     }
  7805.   },
  7806.  
  7807.   /**
  7808.    * Move an Item to the index of another item in its container.
  7809.    * @param   movingID
  7810.    *          The ID of the item to be moved.
  7811.    * @param   destinationID
  7812.    *          The ID of an item to move another item to.
  7813.    */
  7814.   moveToIndexOf: function EMDS_moveToIndexOf(movingID, destinationID) {
  7815.     var extensions = gRDF.GetResource(RDFURI_ITEM_ROOT);
  7816.     var ctr = getContainer(this._inner, extensions);
  7817.     var item = gRDF.GetResource(movingID);
  7818.     var index = ctr.IndexOf(gRDF.GetResource(destinationID));
  7819.     if (index == -1)
  7820.       index = 1; // move to the beginning if destinationID is not found
  7821.     this._inner.beginUpdateBatch();
  7822.     ctr.RemoveElement(item, true);
  7823.     ctr.InsertElementAt(item, index, true);
  7824.     this._inner.endUpdateBatch();
  7825.     this.Flush();
  7826.   },
  7827.  
  7828.   /**
  7829.    * Sorts addons of the specified type by the specified property starting from
  7830.    * the top of their container. If the addons are already sorted then no action
  7831.    * is performed.
  7832.    * @param   type
  7833.    *          The nsIUpdateItem type of the items to sort.
  7834.    * @param   propertyName
  7835.    *          The RDF property name used for sorting.
  7836.    * @param   isAscending
  7837.    *          true to sort ascending and false to sort descending
  7838.    */
  7839.   sortTypeByProperty: function EMDS_sortTypeByProperty(type, propertyName, isAscending) {
  7840.     var items = [];
  7841.     var ctr = getContainer(this._inner, this._itemRoot);
  7842.     var elements = ctr.GetElements();
  7843.     // Base 0 ordinal for checking against the existing order after sorting
  7844.     var ordinal = 0;
  7845.     while (elements.hasMoreElements()) {
  7846.       var item = elements.getNext().QueryInterface(Ci.nsIRDFResource);
  7847.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  7848.       var itemType = this.getItemProperty(id, "type");
  7849.       if (itemType & type) {
  7850.         items.push({ item   : item,
  7851.                      ordinal: ordinal,
  7852.                      sortkey: this.getItemProperty(id, propertyName) });
  7853.         ordinal++;
  7854.       }
  7855.     }
  7856.  
  7857.     var direction = isAscending ? 1 : -1;
  7858.     // Locale sensitive sort
  7859.     function compare(a, b) {
  7860.       return String.localeCompare(a.sortkey, b.sortkey) * direction;
  7861.     }
  7862.     items.sort(compare);
  7863.  
  7864.     // Check if there are any changes in the order of the items
  7865.     var isDirty = false;
  7866.     for (var i = 0; i < items.length; i++) {
  7867.       if (items[i].ordinal != i) {
  7868.         isDirty = true;
  7869.         break;
  7870.       }
  7871.     }
  7872.  
  7873.     // If there are no changes then early return to avoid the perf impact
  7874.     if (!isDirty)
  7875.       return;
  7876.  
  7877.     // Reorder the items by moving them to the top of the container
  7878.     this.beginUpdateBatch();
  7879.     for (i = 0; i < items.length; i++) {
  7880.       ctr.RemoveElement(items[i].item, true);
  7881.       ctr.InsertElementAt(items[i].item, i + 1, true);
  7882.     }
  7883.     this.endUpdateBatch();
  7884.     this.Flush();
  7885.   },
  7886.  
  7887.   /**
  7888.    * Determines if an Item is an active download
  7889.    * @param   id
  7890.    *          The ID of the item. This will be a uri scheme without the
  7891.    *          em item prefix so getProperty shouldn't be used.
  7892.    * @returns true if the item is an active download, false otherwise.
  7893.    */
  7894.   isDownloadItem: function EMDS_isDownloadItem(id) {
  7895.     var downloadURL = stringData(this.GetTarget(gRDF.GetResource(id), EM_R("downloadURL"), true));
  7896.     return downloadURL && downloadURL != "";
  7897.   },
  7898.  
  7899.   /**
  7900.    * Adds an entry representing an active download to the appropriate container
  7901.    * @param   addon
  7902.    *          An object implementing nsIUpdateItem for the addon being
  7903.    *          downloaded.
  7904.    */
  7905.   addDownload: function EMDS_addDownload(addon) {
  7906.     // Updates have already been added to the datasource so we just update the
  7907.     // download state.
  7908.     if (addon.id != addon.xpiURL) {
  7909.       this.updateDownloadState(PREFIX_ITEM_URI + addon.id, "waiting");
  7910.       return;
  7911.     }
  7912.     var res = gRDF.GetResource(addon.xpiURL);
  7913.     this._setProperty(this._inner, res, EM_R("name"), EM_L(addon.name));
  7914.     this._setProperty(this._inner, res, EM_R("version"), EM_L(addon.version));
  7915.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(addon.iconURL));
  7916.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(addon.xpiURL));
  7917.     this._setProperty(this._inner, res, EM_R("type"), EM_I(addon.type));
  7918.  
  7919.     var ctr = getContainer(this._inner, this._itemRoot);
  7920.     if (ctr.IndexOf(res) == -1)
  7921.       ctr.AppendElement(res);
  7922.  
  7923.     this.updateDownloadState(addon.xpiURL, "waiting");
  7924.     this.Flush();
  7925.   },
  7926.  
  7927.   /**
  7928.    * Adds an entry representing an item that is incompatible and is being
  7929.    * checked for a compatibility update.
  7930.    * @param   name
  7931.    *          The display name of the item being checked
  7932.    * @param   url
  7933.    *          The URL string of the xpi file that has been staged.
  7934.    * @param   type
  7935.    *          The nsIUpdateItem type of the item
  7936.    * @param   version
  7937.    *          The version of the item
  7938.    */
  7939.   addIncompatibleUpdateItem: function EMDS_addIncompatibleUpdateItem(name, url, type, version) {
  7940.     var iconURL = (type == Ci.nsIUpdateItem.TYPE_THEME) ? URI_GENERIC_ICON_THEME :
  7941.                                                           URI_GENERIC_ICON_XPINSTALL;
  7942.     var extensionsStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  7943.     var updateMsg = extensionsStrings.formatStringFromName("incompatibleUpdateMessage",
  7944.                                                            [BundleManager.appName, name], 2)
  7945.  
  7946.     var res = gRDF.GetResource(url);
  7947.     this._setProperty(this._inner, res, EM_R("name"), EM_L(name));
  7948.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(iconURL));
  7949.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(url));
  7950.     this._setProperty(this._inner, res, EM_R("type"), EM_I(type));
  7951.     this._setProperty(this._inner, res, EM_R("version"), EM_L(version));
  7952.     this._setProperty(this._inner, res, EM_R("incompatibleUpdate"), EM_L("true"));
  7953.     this._setProperty(this._inner, res, EM_R("description"), EM_L(updateMsg));
  7954.  
  7955.     var ctr = getContainer(this._inner, this._itemRoot);
  7956.     if (ctr.IndexOf(res) == -1)
  7957.       ctr.AppendElement(res);
  7958.  
  7959.     this.updateDownloadState(url, "incompatibleUpdate");
  7960.     this.Flush();
  7961.   },
  7962.  
  7963.   /**
  7964.    * Removes an active download from the appropriate container
  7965.    * @param   url
  7966.    *          The URL string of the active download to be removed
  7967.    */
  7968.   removeDownload: function EMDS_removeDownload(url) {
  7969.     var res = gRDF.GetResource(url);
  7970.     var ctr = getContainer(this._inner, this._itemRoot);
  7971.     if (ctr.IndexOf(res) != -1)
  7972.       ctr.RemoveElement(res, true);
  7973.     this._cleanResource(res);
  7974.     this.updateDownloadState(url, null);
  7975.     this.Flush();
  7976.   },
  7977.  
  7978.   /**
  7979.    * A hash of RDF resource values (e.g. Add-on IDs or XPI URLs) that represent
  7980.    * installation progress for a single browser session.
  7981.    */
  7982.   _progressData: { },
  7983.  
  7984.   /**
  7985.    * Updates the install progress data for a given ID (e.g. Add-on IDs or
  7986.    * XPI URLs).
  7987.    * @param   id
  7988.    *          The URL string of the active download to be removed
  7989.    * @param   state
  7990.    *          The current state in the installation process. If null the object
  7991.    *          is deleted from _progressData.
  7992.    */
  7993.   updateDownloadState: function EMDS_updateDownloadState(id, state) {
  7994.     if (!state) {
  7995.       if (id in this._progressData)
  7996.         delete this._progressData[id];
  7997.       return;
  7998.     }
  7999.     else {
  8000.       if (!(id in this._progressData))
  8001.         this._progressData[id] = { };
  8002.       this._progressData[id].state = state;
  8003.     }
  8004.     var item = gRDF.GetResource(id);
  8005.     this._updateProperty(item, "state");
  8006.   },
  8007.  
  8008.   updateDownloadProgress: function EMDS_updateDownloadProgress(id, progress) {
  8009.     if (!progress) {
  8010.       if (!(id in this._progressData))
  8011.         return;
  8012.       this._progressData[id].progress = null;
  8013.     }
  8014.     else {
  8015.       if (!(id in this._progressData))
  8016.         this.updateDownloadState(id, "downloading");
  8017.  
  8018.       if (this._progressData[id].progress == progress)
  8019.         return;
  8020.  
  8021.       this._progressData[id].progress = progress;
  8022.     }
  8023.     var item = gRDF.GetResource(id);
  8024.     this._updateProperty(item, "progress");
  8025.   },
  8026.  
  8027.   /**
  8028.    * A GUID->location-key hash of items that are visible to the application.
  8029.    * These are items that show up in the Extension/Themes etc UI. If there is
  8030.    * an instance of the same item installed in Install Locations of differing
  8031.    * profiles, the item at the highest priority location will appear in this
  8032.    * list.
  8033.    */
  8034.   visibleItems: { },
  8035.  
  8036.   /**
  8037.    * Walk the list of installed items and determine what the visible list is,
  8038.    * based on which items are visible at the highest priority locations.
  8039.    */
  8040.   _buildVisibleItemList: function EMDS__buildVisibleItemList() {
  8041.     var ctr = getContainer(this, this._itemRoot);
  8042.     var items = ctr.GetElements();
  8043.     while (items.hasMoreElements()) {
  8044.       var item = items.getNext().QueryInterface(Ci.nsIRDFResource);
  8045.       // Resource URIs adopt the format: location-key,item-id
  8046.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8047.       this.visibleItems[id] = this.getItemProperty(id, "installLocation");
  8048.     }
  8049.   },
  8050.  
  8051.   /**
  8052.    * Updates an item's location in the visible item list.
  8053.    * @param   id
  8054.    *          The GUID of the item to update
  8055.    * @param   locationKey
  8056.    *          The name of the Install Location where the item is installed.
  8057.    * @param   forceReplace
  8058.    *          true if the new location should be used, regardless of its
  8059.    *          priority relationship to existing entries, false if the location
  8060.    *          should only be updated if its priority is lower than the existing
  8061.    *          value.
  8062.    */
  8063.   updateVisibleList: function EMDS_updateVisibleList(id, locationKey, forceReplace) {
  8064.     if (id in this.visibleItems && this.visibleItems[id]) {
  8065.       var oldLocation = InstallLocations.get(this.visibleItems[id]);
  8066.       var newLocation = InstallLocations.get(locationKey);
  8067.       if (forceReplace || !oldLocation || newLocation.priority < oldLocation.priority)
  8068.         this.visibleItems[id] = locationKey;
  8069.     }
  8070.     else
  8071.       this.visibleItems[id] = locationKey;
  8072.   },
  8073.  
  8074.   /**
  8075.    * Load the Extensions Datasource from disk.
  8076.    */
  8077.   loadExtensions: function EMDS_loadExtensions() {
  8078.     var extensionsFile  = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  8079.     try {
  8080.       this._inner = gRDF.GetDataSourceBlocking(getURLSpecFromFile(extensionsFile));
  8081.     }
  8082.     catch (e) {
  8083.       ERROR("Datasource::loadExtensions: removing corrupted extensions datasource " +
  8084.             " file = " + extensionsFile.path + ", exception = " + e + "\n");
  8085.       extensionsFile.remove(false);
  8086.       return;
  8087.     }
  8088.  
  8089.     var cu = Cc["@mozilla.org/rdf/container-utils;1"].
  8090.              getService(Ci.nsIRDFContainerUtils);
  8091.     cu.MakeSeq(this._inner, this._itemRoot);
  8092.  
  8093.     this._buildVisibleItemList();
  8094.   },
  8095.  
  8096.   /**
  8097.    * See nsIExtensionManager.idl
  8098.    */
  8099.   onUpdateStarted: function EMDS_onUpdateStarted() {
  8100.     LOG("Datasource: Update Started");
  8101.   },
  8102.  
  8103.   /**
  8104.    * See nsIExtensionManager.idl
  8105.    */
  8106.   onUpdateEnded: function EMDS_onUpdateEnded() {
  8107.     LOG("Datasource: Update Ended");
  8108.   },
  8109.  
  8110.   /**
  8111.    * See nsIExtensionManager.idl
  8112.    */
  8113.   onAddonUpdateStarted: function EMDS_onAddonUpdateStarted(addon) {
  8114.     if (!addon)
  8115.       throw Cr.NS_ERROR_INVALID_ARG;
  8116.  
  8117.     LOG("Datasource: Addon Update Started: " + addon.id);
  8118.     this.updateProperty(addon.id, "availableUpdateURL");
  8119.   },
  8120.  
  8121.   /**
  8122.    * See nsIExtensionManager.idl
  8123.    */
  8124.   onAddonUpdateEnded: function EMDS_onAddonUpdateEnded(addon, status) {
  8125.     if (!addon)
  8126.       throw Cr.NS_ERROR_INVALID_ARG;
  8127.  
  8128.     LOG("Datasource: Addon Update Ended: " + addon.id + ", status: " + status);
  8129.     var url = null, hash = null, version = null;
  8130.     var updateAvailable = status == Ci.nsIAddonUpdateCheckListener.STATUS_UPDATE;
  8131.     if (updateAvailable) {
  8132.       url = EM_L(addon.xpiURL);
  8133.       if (addon.xpiHash)
  8134.         hash = EM_L(addon.xpiHash);
  8135.       version = EM_L(addon.version);
  8136.     }
  8137.     this.setItemProperties(addon.id, {
  8138.       availableUpdateURL: url,
  8139.       availableUpdateHash: hash,
  8140.       availableUpdateVersion: version
  8141.     });
  8142.     this.updateProperty(addon.id, "availableUpdateURL");
  8143.   },
  8144.  
  8145.   /////////////////////////////////////////////////////////////////////////////
  8146.   // nsIRDFDataSource
  8147.   get URI() {
  8148.     return "rdf:extensions";
  8149.   },
  8150.  
  8151.   GetSource: function EMDS_GetSource(property, target, truthValue) {
  8152.     return this._inner.GetSource(property, target, truthValue);
  8153.   },
  8154.  
  8155.   GetSources: function EMDS_GetSources(property, target, truthValue) {
  8156.     return this._inner.GetSources(property, target, truthValue);
  8157.   },
  8158.  
  8159.   /**
  8160.    * Gets an URL to a theme's image file
  8161.    * @param   item
  8162.    *          The RDF Resource representing the item
  8163.    * @param   fileName
  8164.    *          The file to locate a URL for
  8165.    * @param   fallbackURL
  8166.    *          If the location fails, supply this URL instead
  8167.    * @returns An RDF Resource to the URL discovered, or the fallback
  8168.    *          if the discovery failed.
  8169.    */
  8170.   _getThemeImageURL: function EMDS__getThemeImageURL(item, fileName, fallbackURL) {
  8171.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8172.     var installLocation = this._em.getInstallLocation(id);
  8173.     if (!installLocation)
  8174.       return fallbackURL;
  8175.     var file = installLocation.getItemFile(id, fileName)
  8176.     if (file.exists())
  8177.       return gRDF.GetResource(getURLSpecFromFile(file));
  8178.  
  8179.     if (id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)) {
  8180.       var jarFile = getFile(KEY_APPDIR, [DIR_CHROME, FILE_DEFAULT_THEME_JAR]);
  8181.       var url = "jar:" + getURLSpecFromFile(jarFile) + "!/" + fileName;
  8182.       return gRDF.GetResource(url);
  8183.     }
  8184.  
  8185.     return fallbackURL ? gRDF.GetResource(fallbackURL) : null;
  8186.   },
  8187.  
  8188.   /**
  8189.    * Get the em:iconURL property (icon url of the item)
  8190.    */
  8191.   _rdfGet_iconURL: function EMDS__rdfGet_iconURL(item, property) {
  8192.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8193.     var type = this.getItemProperty(id, "type");
  8194.     if (type & Ci.nsIUpdateItem.TYPE_THEME)
  8195.       return this._getThemeImageURL(item, "icon.png", URI_GENERIC_ICON_THEME);
  8196.  
  8197.     if (inSafeMode())
  8198.       return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  8199.  
  8200.     var hasIconURL = this._inner.hasArcOut(item, property);
  8201.     // If the addon doesn't have an IconURL property or it is disabled use the
  8202.     // generic icon URL instead.
  8203.     if (!hasIconURL || this.getItemProperty(id, "isDisabled") == "true")
  8204.       return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  8205.     var iconURL = stringData(this._inner.GetTarget(item, property, true));
  8206.     try {
  8207.       var uri = newURI(iconURL);
  8208.       var scheme = uri.scheme;
  8209.       // Only allow chrome URIs or when installing http(s) URIs.
  8210.       if (scheme == "chrome" || (scheme == "http" || scheme == "https") &&
  8211.           this._inner.hasArcOut(item, EM_R("downloadURL")))
  8212.         return null;
  8213.     }
  8214.     catch (e) {
  8215.     }
  8216.     // Use a generic icon URL for addons that have an invalid iconURL.
  8217.     return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  8218.   },
  8219.  
  8220.   /**
  8221.    * Get the em:previewImage property (preview image of the item)
  8222.    */
  8223.   _rdfGet_previewImage: function EMDS__rdfGet_previewImage(item, property) {
  8224.     var type = this.getItemProperty(stripPrefix(item.Value, PREFIX_ITEM_URI), "type");
  8225.     if (type != -1 && type & Ci.nsIUpdateItem.TYPE_THEME)
  8226.       return this._getThemeImageURL(item, "preview.png", null);
  8227.     return null;
  8228.   },
  8229.  
  8230.   /**
  8231.    * If we're in safe mode, the item is disabled by the user or app, or the
  8232.    * item is to be upgraded force the generic about dialog for the item.
  8233.    */
  8234.   _rdfGet_aboutURL: function EMDS__rdfGet_aboutURL(item, property) {
  8235.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8236.     if (inSafeMode() || this.getItemProperty(id, "isDisabled") == "true" ||
  8237.         this.getItemProperty(id, "opType") == OP_NEEDS_UPGRADE)
  8238.       return EM_L("");
  8239.  
  8240.     return null;
  8241.   },
  8242.  
  8243.   _rdfGet_installDate: function EMDS__rdfGet_installDate(item, property) {
  8244.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8245.     var key = this.getItemProperty(id, "installLocation");
  8246.     if (key && key in StartupCache.entries && id in StartupCache.entries[key] &&
  8247.         StartupCache.entries[key][id] && StartupCache.entries[key][id].mtime)
  8248.       return EM_D(StartupCache.entries[key][id].mtime * 1000000);
  8249.     return null;
  8250.   },
  8251.  
  8252.   /**
  8253.    * Get the em:compatible property (whether or not this item is compatible)
  8254.    */
  8255.   _rdfGet_compatible: function EMDS__rdfGet_compatible(item, property) {
  8256.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8257.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  8258.     if (!targetAppInfo) {
  8259.       // When installing a new addon targetAppInfo does not exist yet
  8260.       if (this.getItemProperty(id, "opType") == OP_NEEDS_INSTALL)
  8261.         return EM_L("true");
  8262.       return EM_L("false");
  8263.     }
  8264.  
  8265.     getVersionChecker();
  8266.     var appVersion = targetAppInfo.appID == TOOLKIT_ID ? gApp.platformVersion : gApp.version;
  8267.     if (gVersionChecker.compare(targetAppInfo.maxVersion, appVersion) < 0 ||
  8268.         gVersionChecker.compare(appVersion, targetAppInfo.minVersion) < 0) {
  8269.       // OK, this item is incompatible.
  8270.       return EM_L("false");
  8271.     }
  8272.     return EM_L("true");
  8273.   },
  8274.  
  8275.   /**
  8276.    * Get the providesUpdatesSecurely property (whether or not this item has a
  8277.    * secure update mechanism)
  8278.    */
  8279.   _rdfGet_providesUpdatesSecurely: function EMDS__rdfGet_providesUpdatesSecurely(item, property) {
  8280.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8281.     if (this.getItemProperty(id, "updateKey") ||
  8282.         !this.getItemProperty(id, "updateURL") ||
  8283.         this.getItemProperty(id, "updateURL").substring(0, 6) == "https:")
  8284.       return EM_L("true");
  8285.     return EM_L("false");
  8286.   },
  8287.  
  8288.   /**
  8289.    * Get the em:blocklisted property (whether or not this item is blocklisted)
  8290.    */
  8291.   _rdfGet_blocklisted: function EMDS__rdfGet_blocklisted(item, property) {
  8292.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8293.     var version = this.getItemProperty(id, "version");
  8294.     if (!gBlocklist)
  8295.       gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
  8296.                    getService(Ci.nsIBlocklistService);
  8297.     if (gBlocklist.getAddonBlocklistState(id, version) == Ci.nsIBlocklistService.STATE_BLOCKED)
  8298.       return EM_L("true");
  8299.  
  8300.     return EM_L("false");
  8301.   },
  8302.  
  8303.   /**
  8304.    * Get the em:blocklistedsoft property (whether or not this item is listed in the blocklist
  8305.    * at a low severity)
  8306.    */
  8307.   _rdfGet_blocklistedsoft: function EMDS__rdfGet_blocklistedsoft(item, property) {
  8308.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8309.     var version = this.getItemProperty(id, "version");
  8310.     if (!gBlocklist)
  8311.       gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
  8312.                    getService(Ci.nsIBlocklistService);
  8313.     if (gBlocklist.getAddonBlocklistState(id, version) == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
  8314.       return EM_L("true");
  8315.  
  8316.     return EM_L("false");
  8317.   },
  8318.  
  8319.   /**
  8320.    * Get the em:state property (represents the current phase of an install).
  8321.    */
  8322.   _rdfGet_state: function EMDS__rdfGet_state(item, property) {
  8323.     var id = item.Value;
  8324.     if (id in this._progressData)
  8325.       return EM_L(this._progressData[id].state);
  8326.     return null;
  8327.   },
  8328.  
  8329.   /**
  8330.    * Get the em:progress property from the _progressData js object. By storing
  8331.    * progress which is updated repeastedly during a download we avoid
  8332.    * repeastedly writing it to the rdf file.
  8333.    */
  8334.   _rdfGet_progress: function EMDS__rdfGet_progress(item, property) {
  8335.     var id = item.Value;
  8336.     if (id in this._progressData)
  8337.       return EM_I(this._progressData[id].progress);
  8338.     return null;
  8339.   },
  8340.  
  8341.   /**
  8342.    * Get the em:appManaged property. This prevents extensions from hiding
  8343.    * extensions installed into locations other than the app-global location.
  8344.    */
  8345.   _rdfGet_appManaged: function EMDS__rdfGet_appManaged(item, property) {
  8346.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8347.     var locationKey = this.getItemProperty(id, "installLocation");
  8348.     if (locationKey != KEY_APP_GLOBAL && locationKey != KEY_GRE_GLOBAL)
  8349.       return EM_L("false");
  8350.     return null;
  8351.   },
  8352.  
  8353.   /**
  8354.    * Get the em:hidden property. This prevents extensions from hiding
  8355.    * extensions installed into locations other than restricted locations.
  8356.    */
  8357.   _rdfGet_hidden: function EMDS__rdfGet_hidden(item, property) {
  8358.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8359.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  8360.     if (!installLocation || !installLocation.restricted)
  8361.       return EM_L("false");
  8362.     return null;
  8363.   },
  8364.  
  8365.   /**
  8366.    * Get the em:locked property. This prevents extensions from locking
  8367.    * extensions installed into locations other than restricted locations.
  8368.    */
  8369.   _rdfGet_locked: function EMDS__rdfGet_locked(item, property) {
  8370.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8371.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  8372.     if (!installLocation || !installLocation.restricted)
  8373.       return EM_L("false");
  8374.     return null;
  8375.   },
  8376.  
  8377.   /**
  8378.    * Get the em:satisfiesDependencies property - literal string "false" for
  8379.    * dependencies not satisfied (e.g. dependency disabled, incorrect version,
  8380.    * not installed etc.), and literal string "true" for dependencies satisfied.
  8381.    */
  8382.   _rdfGet_satisfiesDependencies: function EMDS__rdfGet_satisfiesDependencies(item, property) {
  8383.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8384.     if (this.satisfiesDependencies(id))
  8385.       return EM_L("true");
  8386.     return EM_L("false");
  8387.   },
  8388.  
  8389.   /**
  8390.    * Get the em:opType property (controls widget state for the EM UI)
  8391.    * from the Startup Cache (e.g. extensions.cache)
  8392.    */
  8393.   _rdfGet_opType: function EMDS__rdfGet_opType(item, property) {
  8394.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8395.     var key = this.getItemProperty(id, "installLocation");
  8396.     if (key in StartupCache.entries && id in StartupCache.entries[key] &&
  8397.         StartupCache.entries[key][id] && StartupCache.entries[key][id].op != OP_NONE)
  8398.       return EM_L(StartupCache.entries[key][id].op);
  8399.     return null;
  8400.   },
  8401.  
  8402.   /**
  8403.    * Gets a localizable property. Install Manifests are generally only in one
  8404.    * language, however an item can customize by providing localized prefs in
  8405.    * the form:
  8406.    *
  8407.    *    extensions.{GUID}.[name|description|creator|homepageURL]
  8408.    *
  8409.    * to specify localized text for each of these properties.
  8410.    */
  8411.   _getLocalizablePropertyValue: function EMDS__getLocalizablePropertyValue(item, property) {
  8412.     // These are localizable properties that a language pack supplied by the
  8413.     // Extension may override.
  8414.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/,
  8415.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) +
  8416.                     stripPrefix(property.Value, PREFIX_NS_EM);
  8417.     try {
  8418.       var value = gPref.getComplexValue(prefName,
  8419.                                         Ci.nsIPrefLocalizedString);
  8420.       if (value.data)
  8421.         return EM_L(value.data);
  8422.     }
  8423.     catch (e) {
  8424.     }
  8425.  
  8426.     var localized = findClosestLocalizedResource(this._inner, item);
  8427.     if (localized) {
  8428.       var value = this._inner.GetTarget(localized, property, true);
  8429.       return value ? value : EM_L("");
  8430.     }
  8431.     return null;
  8432.   },
  8433.  
  8434.   /**
  8435.    * Get the em:name property (name of the item)
  8436.    */
  8437.   _rdfGet_name: function EMDS__rdfGet_name(item, property) {
  8438.     return this._getLocalizablePropertyValue(item, property);
  8439.   },
  8440.  
  8441.   /**
  8442.    * Get the em:description property (description of the item)
  8443.    */
  8444.   _rdfGet_description: function EMDS__rdfGet_description(item, property) {
  8445.     return this._getLocalizablePropertyValue(item, property);
  8446.   },
  8447.  
  8448.   /**
  8449.    * Get the em:creator property (creator of the item)
  8450.    */
  8451.   _rdfGet_creator: function EMDS__rdfGet_creator(item, property) {
  8452.     return this._getLocalizablePropertyValue(item, property);
  8453.   },
  8454.  
  8455.   /**
  8456.    * Get the em:homepageURL property (homepage URL of the item)
  8457.    */
  8458.   _rdfGet_homepageURL: function EMDS__rdfGet_homepageURL(item, property) {
  8459.     return this._getLocalizablePropertyValue(item, property);
  8460.   },
  8461.   
  8462.   _rdfGet_availableUpdateInfo: function EMDS__rdfGet_availableUpdateInfo(item, property) {
  8463.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8464.     var uri = stringData(this._inner.GetTarget(item, EM_R("availableUpdateInfo"), true));
  8465.     if (uri) {
  8466.       uri = escapeAddonURI(this.getItemForID(id), null, uri, this);
  8467.       return EM_L(uri);
  8468.     }
  8469.     return null;
  8470.   },
  8471.  
  8472.   /**
  8473.    * Get the em:isDisabled property. This will be true if the item has a
  8474.    * appDisabled or a userDisabled property that is true or OP_NEEDS_ENABLE.
  8475.    */
  8476.   _rdfGet_isDisabled: function EMDS__rdfGet_isDisabled(item, property) {
  8477.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8478.     if (this.getItemProperty(id, "userDisabled") == "true" ||
  8479.         this.getItemProperty(id, "appDisabled") == "true" ||
  8480.         this.getItemProperty(id, "userDisabled") == OP_NEEDS_ENABLE ||
  8481.         this.getItemProperty(id, "appDisabled") == OP_NEEDS_ENABLE)
  8482.       return EM_L("true");
  8483.     return EM_L("false");
  8484.   },
  8485.  
  8486.   _rdfGet_addonID: function EMDS__rdfGet_addonID(item, property) {
  8487.     var id = this._inner.GetTarget(item, EM_R("downloadURL"), true) ? item.Value :
  8488.                                                                       stripPrefix(item.Value, PREFIX_ITEM_URI);
  8489.     return EM_L(id);
  8490.   },
  8491.  
  8492.   /**
  8493.    * Get the em:updateable property - this specifies whether the item is
  8494.    * allowed to be updated
  8495.    */
  8496.   _rdfGet_updateable: function EMDS__rdfGet_updateable(item, property) {
  8497.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8498.     var opType = this.getItemProperty(id, "opType");
  8499.     if (opType != OP_NONE || this.getItemProperty(id, "appManaged") == "true")
  8500.       return EM_L("false");
  8501.  
  8502.     if (getPref("getBoolPref", (PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, id), false)) == true)
  8503.       return EM_L("false");
  8504.  
  8505.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  8506.     if (!installLocation || !installLocation.canAccess)
  8507.       return EM_L("false");
  8508.  
  8509.     return EM_L("true");
  8510.   },
  8511.  
  8512.   /**
  8513.    * See nsIRDFDataSource.idl
  8514.    */
  8515.   GetTarget: function EMDS_GetTarget(source, property, truthValue) {
  8516.     if (!source)
  8517.       return null;
  8518.  
  8519.     var target = null;
  8520.     var getter = "_rdfGet_" + stripPrefix(property.Value, PREFIX_NS_EM);
  8521.     if (getter in this)
  8522.       target = this[getter](source, property);
  8523.  
  8524.     return target || this._inner.GetTarget(source, property, truthValue);
  8525.   },
  8526.  
  8527.   /**
  8528.    * Gets an enumeration of values of a localizable property. Install Manifests
  8529.    * are generally only in one language, however an item can customize by
  8530.    * providing localized prefs in the form:
  8531.    *
  8532.    *    extensions.{GUID}.[contributor].1
  8533.    *    extensions.{GUID}.[contributor].2
  8534.    *    extensions.{GUID}.[contributor].3
  8535.    *    ...
  8536.    *
  8537.    * to specify localized text for each of these properties.
  8538.    */
  8539.   _getLocalizablePropertyValues: function EMDS__getLocalizablePropertyValues(item, property) {
  8540.     // These are localizable properties that a language pack supplied by the
  8541.     // Extension may override.
  8542.     var values = [];
  8543.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/,
  8544.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) +
  8545.                     stripPrefix(property.Value, PREFIX_NS_EM);
  8546.     var i = 0;
  8547.     while (true) {
  8548.       try {
  8549.         var value = gPref.getComplexValue(prefName + "." + ++i,
  8550.                                           Ci.nsIPrefLocalizedString);
  8551.         if (value.data)
  8552.           values.push(EM_L(value.data));
  8553.       }
  8554.       catch (e) {
  8555.         try {
  8556.           var value = gPref.getComplexValue(prefName,
  8557.                                             Ci.nsIPrefLocalizedString);
  8558.           if (value.data)
  8559.             values.push(EM_L(value.data));
  8560.         }
  8561.         catch (e) {
  8562.         }
  8563.         break;
  8564.       }
  8565.     }
  8566.     if (values.length > 0)
  8567.       return values;
  8568.  
  8569.     var localized = findClosestLocalizedResource(this._inner, item);
  8570.     if (localized) {
  8571.       var targets = this._inner.GetTargets(localized, property, true);
  8572.       while (targets.hasMoreElements())
  8573.         values.push(targets.getNext());
  8574.       return values;
  8575.     }
  8576.     return null;
  8577.   },
  8578.  
  8579.   /**
  8580.    * Get the em:developer property (developers of the extension)
  8581.    */
  8582.   _rdfGets_developer: function EMDS__rdfGets_developer(item, property) {
  8583.     return this._getLocalizablePropertyValues(item, property);
  8584.   },
  8585.  
  8586.   /**
  8587.    * Get the em:translator property (translators of the extension)
  8588.    */
  8589.   _rdfGets_translator: function EMDS__rdfGets_translator(item, property) {
  8590.     return this._getLocalizablePropertyValues(item, property);
  8591.   },
  8592.  
  8593.   /**
  8594.    * Get the em:contributor property (contributors to the extension)
  8595.    */
  8596.   _rdfGets_contributor: function EMDS__rdfGets_contributor(item, property) {
  8597.     return this._getLocalizablePropertyValues(item, property);
  8598.   },
  8599.  
  8600.   /**
  8601.    * See nsIRDFDataSource.idl
  8602.    */
  8603.   GetTargets: function EMDS_GetTargets(source, property, truthValue) {
  8604.     if (!source)
  8605.       return null;
  8606.  
  8607.     var ary = null;
  8608.     var propertyName = stripPrefix(property.Value, PREFIX_NS_EM);
  8609.     var getter = "_rdfGets_" + propertyName;
  8610.     if (getter in this)
  8611.       ary = this[getter](source, property);
  8612.     else {
  8613.       // The template builder calls GetTargets when single value properties
  8614.       // are used in a triple.
  8615.       getter = "_rdfGet_" + propertyName;
  8616.       if (getter in this)
  8617.         ary = [ this[getter](source, property) ];
  8618.     }
  8619.  
  8620.     return ary ? new ArrayEnumerator(ary)
  8621.                : this._inner.GetTargets(source, property, truthValue);
  8622.   },
  8623.  
  8624.   Assert: function EMDS_Assert(source, property, target, truthValue) {
  8625.     this._inner.Assert(source, property, target, truthValue);
  8626.   },
  8627.  
  8628.   Unassert: function EMDS_Unassert(source, property, target) {
  8629.     this._inner.Unassert(source, property, target);
  8630.   },
  8631.  
  8632.   Change: function EMDS_Change(source, property, oldTarget, newTarget) {
  8633.     this._inner.Change(source, property, oldTarget, newTarget);
  8634.   },
  8635.  
  8636.   Move: function EMDS_Move(oldSource, newSource, property, target) {
  8637.     this._inner.Move(oldSource, newSource, property, target);
  8638.   },
  8639.  
  8640.   HasAssertion: function EMDS_HasAssertion(source, property, target, truthValue) {
  8641.     if (!source || !property || !target)
  8642.       return false;
  8643.  
  8644.     var getter = "_rdfGet_" + stripPrefix(property.Value, PREFIX_NS_EM);
  8645.     if (getter in this)
  8646.       return this[getter](source, property) == target;
  8647.     return this._inner.HasAssertion(source, property, target, truthValue);
  8648.   },
  8649.  
  8650.   _observers: [],
  8651.   AddObserver: function EMDS_AddObserver(observer) {
  8652.     for (var i = 0; i < this._observers.length; ++i) {
  8653.       if (this._observers[i] == observer)
  8654.         return;
  8655.     }
  8656.     this._observers.push(observer);
  8657.     this._inner.AddObserver(observer);
  8658.   },
  8659.  
  8660.   RemoveObserver: function EMDS_RemoveObserver(observer) {
  8661.     for (var i = 0; i < this._observers.length; ++i) {
  8662.       if (this._observers[i] == observer)
  8663.         this._observers.splice(i, 1);
  8664.     }
  8665.     this._inner.RemoveObserver(observer);
  8666.   },
  8667.  
  8668.   ArcLabelsIn: function EMDS_ArcLabelsIn(node) {
  8669.     return this._inner.ArcLabelsIn(node);
  8670.   },
  8671.  
  8672.   ArcLabelsOut: function EMDS_ArcLabelsOut(source) {
  8673.     return this._inner.ArcLabelsOut(source);
  8674.   },
  8675.  
  8676.   GetAllResources: function EMDS_GetAllResources() {
  8677.     return this._inner.GetAllResources();
  8678.   },
  8679.  
  8680.   IsCommandEnabled: function EMDS_IsCommandEnabled(sources, command, arguments) {
  8681.     return this._inner.IsCommandEnabled(sources, command, arguments);
  8682.   },
  8683.  
  8684.   DoCommand: function EMDS_DoCommand(sources, command, arguments) {
  8685.     this._inner.DoCommand(sources, command, arguments);
  8686.   },
  8687.  
  8688.   GetAllCmds: function EMDS_GetAllCmds(source) {
  8689.     return this._inner.GetAllCmds(source);
  8690.   },
  8691.  
  8692.   hasArcIn: function EMDS_hasArcIn(node, arc) {
  8693.     return this._inner.hasArcIn(node, arc);
  8694.   },
  8695.  
  8696.   hasArcOut: function EMDS_hasArcOut(source, arc) {
  8697.     return this._inner.hasArcOut(source, arc);
  8698.   },
  8699.  
  8700.   beginUpdateBatch: function EMDS_beginUpdateBatch() {
  8701.     return this._inner.beginUpdateBatch();
  8702.   },
  8703.  
  8704.   endUpdateBatch: function EMDS_endUpdateBatch() {
  8705.     return this._inner.endUpdateBatch();
  8706.   },
  8707.  
  8708.   /**
  8709.    * See nsIRDFRemoteDataSource.idl
  8710.    */
  8711.   get loaded() {
  8712.     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  8713.   },
  8714.  
  8715.   Init: function EMDS_Init(uri) {
  8716.   },
  8717.  
  8718.   Refresh: function EMDS_Refresh(blocking) {
  8719.   },
  8720.  
  8721.   Flush: function EMDS_Flush() {
  8722.     // For some operations we block repeated flushing until all operations
  8723.     // are complete to reduce file accesses that can trigger bug 431065
  8724.     if (!gAllowFlush) {
  8725.       gDSNeedsFlush = true;
  8726.       return;
  8727.     }
  8728.     if (this._inner instanceof Ci.nsIRDFRemoteDataSource)
  8729.       this._inner.Flush();
  8730.   },
  8731.  
  8732.   FlushTo: function EMDS_FlushTo(uri) {
  8733.   },
  8734.  
  8735.   classDescription: "Extension Manager Data Source",
  8736.   contractID: "@mozilla.org/rdf/datasource;1?name=extensions",
  8737.   classID: Components.ID("{69BB8313-2D4F-45EC-97E0-D39DA58ECCE9}"),
  8738.   _xpcom_factory: {
  8739.     createInstance: function() Cc[ExtensionManager.prototype.contractID].
  8740.                                getService(Ci.nsIExtensionManager).datasource
  8741.   },
  8742.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRDFDataSource,
  8743.                                          Ci.nsIRDFRemoteDataSource])
  8744. };
  8745.  
  8746. function UpdateItem () {}
  8747. UpdateItem.prototype = {
  8748.   /**
  8749.    * See nsIUpdateService.idl
  8750.    */
  8751.   init: function(id, version, installLocationKey, minAppVersion, maxAppVersion,
  8752.                  name, downloadURL, xpiHash, iconURL, updateURL, updateKey, type,
  8753.                  targetAppID) {
  8754.     this._id                  = id;
  8755.     this._version             = version;
  8756.     this._installLocationKey  = installLocationKey;
  8757.     this._minAppVersion       = minAppVersion;
  8758.     this._maxAppVersion       = maxAppVersion;
  8759.     this._name                = name;
  8760.     this._downloadURL         = downloadURL;
  8761.     this._xpiHash             = xpiHash;
  8762.     this._iconURL             = iconURL;
  8763.     this._updateURL           = updateURL;
  8764.     this._updateKey           = updateKey;
  8765.     this._type                = type;
  8766.     this._targetAppID         = targetAppID;
  8767.   },
  8768.  
  8769.   /**
  8770.    * See nsIUpdateService.idl
  8771.    */
  8772.   get id()                { return this._id;                },
  8773.   get version()           { return this._version;           },
  8774.   get installLocationKey(){ return this._installLocationKey;},
  8775.   get minAppVersion()     { return this._minAppVersion;     },
  8776.   get maxAppVersion()     { return this._maxAppVersion;     },
  8777.   get name()              { return this._name;              },
  8778.   get xpiURL()            { return this._downloadURL;       },
  8779.   get xpiHash()           { return this._xpiHash;           },
  8780.   get iconURL()           { return this._iconURL            },
  8781.   get updateRDF()         { return this._updateURL;         },
  8782.   get updateKey()         { return this._updateKey;         },
  8783.   get type()              { return this._type;              },
  8784.   get targetAppID()       { return this._targetAppID;       },
  8785.  
  8786.   /**
  8787.    * See nsIUpdateService.idl
  8788.    */
  8789.   get objectSource() {
  8790.     return { id                 : this._id,
  8791.              version            : this._version,
  8792.              installLocationKey : this._installLocationKey,
  8793.              minAppVersion      : this._minAppVersion,
  8794.              maxAppVersion      : this._maxAppVersion,
  8795.              name               : this._name,
  8796.              xpiURL             : this._downloadURL,
  8797.              xpiHash            : this._xpiHash,
  8798.              iconURL            : this._iconURL,
  8799.              updateRDF          : this._updateURL,
  8800.              updateKey          : this._updateKey,
  8801.              type               : this._type,
  8802.              targetAppID        : this._targetAppID
  8803.            }.toSource();
  8804.   },
  8805.  
  8806.   classDescription: "Update Item",
  8807.   contractID: "@mozilla.org/updates/item;1",
  8808.   classID: Components.ID("{F3294B1C-89F4-46F8-98A0-44E1EAE92518}"),
  8809.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateItem])
  8810. };
  8811.  
  8812. function NSGetModule(compMgr, fileSpec)
  8813.   XPCOMUtils.generateModule([ExtensionManager, ExtensionsDataSource, UpdateItem]);
  8814.  
  8815.